Sommaire

BONJOUR
Musique "Invention BWV988" J.S. BACH 1685-1750

Tutoriel et Help

Library SDL 2


Tutoriel SDL 2

Introduction

La SDL 2 est la nouvelle version de la célèbre bibliothèque de jeux SDL. Découvrez dans cette série comment l'utiliser et ainsi faire des jeux vidéo simplement.

Tutoriels

  • Installation de la SDL 2.0
  • Vos premières fenêtres
  • Afficher son premier sprite avec les nouvelles fonctionnalités de la bibliothèque
  • La gestion des événements et de l'intéraction avec le joueur

Cette page est valide HTML 5 et CSS 3


Tutoriel SDL 2

Installation de la SDL 2.0

Table des matières

  • Navigation
  • II. Introduction
  • III. Installation
    • III-A. Installation à partir des binaires
      • III-A-1. Windows
      • III-A-2. Linux
      • III-A-3. Mac OS X
    • III-B. Compilation à partir des sources
      • III-B-1. Autotools
        • III-B-1-a. Échec de l'étape configure
      • III-B-2. CMake
        • III-B-2-a. En ligne de commande
        • III-B-2-b. Avec l'interface graphique
      • III-B-3. Projet Visual C
  • IV. Création du projet
    • IV-A. Arborescence du projet
    • IV-B. Premier code SDL 2
    • IV-C. Configuration du projet
      • IV-C-1. GCC
      • IV-C-2. Code::Blocks
      • IV-C-3. Microsoft Visual Studio
      • IV-C-4. XCode
  • V. Projets prêts à utiliser
  • VI. Problèmes
    • VI-A. Linux
      • VI-A-1. « Failed to load driver : swrast »
      • VI-A-2. « error while loading shared libraries: libSDL2-2.0.so.0: cannot open shared object file: No such file or directory »
    • VI-B. Windows
      • VI-B-1. « number of arguments doesn't match prototype »
      • « previous declaration of 'SDL_main' was here| »
      • VI-B-3. « undefined reference to `WinMain@16' »
      • VI-B-4. Échec de l'étape de configuration avec l'interface graphique de CMake
      • VI-B-5. Erreur 0xc000007b
  • VII. Remerciements
  • Navigation

Apprenez à installer et configurer votre premier projet SDL 2.0.

II. Introduction

La nouvelle version de la Simple DirectMedia Library (SDL) est enfin arrivée. Celle-ci apporte des évolutions non négligeables mettant au goût du jour la bibliothèque. Il est à nouveau intéressant de se pencher sur celle-ci, notamment car la SDL est une bibliothèque C, facile à utiliser et pratique pour créer des jeux vidéo.

Voyons comment l'installer et créer un premier projet SDL 2.

III. Installation

III-A. Installation à partir des binaires

Les binaires sont les fichiers compilés de la bibliothèque et peuvent être utilisés tels quels. Il est conseillé de les utiliser lorsqu'ils sont disponibles.

III-A-1. Windows

Deux fichiers ont été mis en place pour Windows. Vous devez prendre celui correspondant à votre compilateur :

  • Microsoft Visual C++ ;
  • MinGW.

Il suffit d'extraire l'archive et de placer les fichiers dans votre projet, comme expliqué dans la section : « Arborescence du projetArborescence du projet ». Si vous avez un système d'exploitation et la suite de compilation en 64 bits, prenez les fichiers présents dans le sous-dossier x64 (ou x84-64 pour l'archive pour MinGW). Sinon (ou si vous doutez), prenez les fichiers dans le sous-dossier x86 (ou i686 pour l'archive pour MinGW).

III-A-2. Linux

Pour installer la SDL, il suffit d'utiliser le gestionnaire de paquets de votre distribution. Afin de développer avec la SDL, les paquets à choisir sont ceux de la SDL et leur version de développement (souvent appelés devel ou dev).

III-A-3. Mac OS X

Téléchargez le fichier .dmg et installez-le.

III-B. Compilation à partir des sources

Pour compiler la SDL 2.0 à partir des sources, vous avez plusieurs possibilités :

  • autotools ;
  • CMake ;
  • Projet Microsoft Visual C (Windows seulement).

Les deux premiers outils permettent de générer les fichiers pour compiler un projet, et ceux pour la configuration actuelle de votre machine. Les fichiers générés seront donc des Makefile, ou les fichiers pour le compilateur et l'éditeur de développement installés actuellement sur votre ordinateur. Il ne suffira plus qu'à lancer la compilation dans votre éditeur préféré afin d'obtenir les fichiers finaux de la bibliothèque.

La troisième possibilité n'est compatible qu'avec Windows et l'éditeur Microsoft Visual Studio. En effet, la SDL propose des fichiers .sln, fichiers de projet de Visual Studio, préconfigurés et permettant ainsi de compiler la bibliothèque.

III-B-1. Autotools

Autotools est un ensemble d'outils multiplateformes permettant de générer des Makefile et de configurer le projet suivant votre machine.

Pour compiler la SDL, il suffit d'exécuter les commandes suivantes dans le dossier des sources de la bibliothèque :

./configure
make

et si vous voulez l'installer dans votre système :

make install

Autotools est très simple à utiliser et représente la norme pour les systèmes GNU/Linux et autres Unix. Sous Windows, il sera nécessaire d'avoir cygwin pour générer les Makefile et les exécuter. Il est donc conseillé d'utiliser CMakeCMake pour compiler la SDL sous Windows.

Si vous ne faites pas l'installation, vous pouvez trouver les fichiers .a (bibliothèque statique) dans le dossier build et le fichier .so (bibliothèque dynamique), dans le dossier build/.libs/.

III-B-1-a. Échec de l'étape configure

Le script configure permet de vérifier que votre système est prêt pour compiler la bibliothèque. S'il échoue, il n'y a pas de quoi s'inquiéter. Souvent, cela est dû à une dépendance manquante. Lisez attentivement le message d'erreur afin de trouver quel est le logiciel (ou bibliothèque) manquant. Une fois que vous avez trouvé, installez-le (par exemple, à l'aide de votre gestionnaire de paquets).

III-B-2. CMake

CMake est un programme multiplateforme, tout comme autotools, mais permettant de générer les fichiers de projets pour les éditeurs de développement intégrés (Code::Blocks, Microsoft Visual Studio…). De plus, CMake dispose d'une interface graphique, le rendant plus simple à utiliser.

III-B-2-a. En ligne de commande

Pour la SDL, il est nécessaire de créer un nouveau dossier (par exemple, build) à côté de celui extrait. Ensuite, il suffit d'exécuter à partir du dossier build :

cmake -G « Unix Makefiles » ../dossier_des_sources_de_la_SDL
make

Cet exemple créera un Makefile et est donc parfait pour Linux. L'élément que vous pouvez passer à l'option -G détermine le type de fichier devant être généré. Afin de savoir ce que vous pouvez utiliser comme options, exécutez cmake (sans aucune option) et il vous listera les possibilités.

III-B-2-b. Avec l'interface graphique

Pour MinGW, il faut que le chemin menant au compilateur (dossier bin de MinGW) soit présent dans le PATH. Dans le cas contraire, CMake affichera des erreurs à propos des variables CMAKE_C_COMPILER, CMAKE_C_COMPILER_ENV_VAR…

Le SDK de DirectX est aussi nécessaire pour la compilation de la SDL sous Windows. Toutefois, vous pouvez vous en passer en décochant la variable DIRECTX.

Image non disponible

Une fois l'interface graphique chargée, indiquez le chemin menant au dossier extrait des sources de la SDL 2.0 pour l'entrée « Where is the source code » et indiquez le chemin menant à un dossier build à côté du dossier extrait des sources pour l'entrée « Where to build the binaries ».

Une fois les deux entrées indiquées, vous pouvez cliquer sur le bouton « Configure », choisissez le type de projet à générer et le compilateur à utiliser, puis, si tout s'est correctement déroulé, sur le bouton « Generate ». En cas de réussite, vous aurez les fichiers pour votre éditeur dans le second dossier que vous avez indiqué (« Where to build the binaries »). Pour compiler la bibliothèque, il suffit d'utiliser votre éditeur pour ouvrir et compiler le projet.

Image non disponible

III-B-3. Projet Visual C

Les utilisateurs de Microsoft Visual Studio 2008 et supérieures pourront charger le projet de la bibliothèque présent dans le dossier VisualC et ainsi compiler la SDL 2.0. Il suffit d'ouvrir le fichier .sln correspondant à sa version de Visual Studio et de compiler le projet pour obtenir les fichiers .lib et .dll de la bibliothèque.

Si vous obtenez des erreurs indiquant que le compilateur ne trouve pas les fichiers « dinput.h », « dsound.h », vous devez rajouter le répertoire de fichiers d'entête de DirectX à la configuration du projet.

Pour cela, allez dans les propriétés du projet, puis dans le menu C/C++ et rajoutez le répertoire Include du SDL de DirectX (par défaut : C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include\) pour l'entrée « Répertoires d'inclusion supplémentaires » (« Additional Include Directories »).

Afin d'avoir des performances optimales, compilez la bibliothèque en utilisant la cible « Release ». Les fichiers seront ainsi plus légers et optimisés.

IV. Création du projet

Maintenant que nous avons les fichiers de la SDL, nous pouvons commencer à créer le projet utilisant la bibliothèque.

IV-A. Arborescence du projet

Afin que votre projet soit organisé et facilement distribuable, nous allons créer les dossiers suivants :

  • lib : pour les fichiers des bibliothèques (.a/.so/.lib) ;
  • include : pour les fichiers d'entête (notamment, ceux de la SDL) ;
  • src : pour vos fichiers de code source.

Cette organisation permet d'éviter à ce que vous ayez à installer (ou faire installer) la bibliothèque sur chacun des postes que vous utiliserez : elle est intégrée dans le projet. De plus, l'installation de la bibliothèque dans les répertoires des éditeurs n'est pas recommandée (même si certains sites font de cette façon :(), notamment, car vous forcez l'utilisateur recevant votre projet à installer lui-même la bibliothèque et à reconfigurer le projet, mais aussi, car cela entraîne des oublis. D'autant plus que sous Windows, il n'y a pas d'emplacement déterminé pour mettre les bibliothèques.

Ce qui nous donne l'arborescence suivante :

/
include
- SDL2
- - …
- - tous les fichiers .h de la SDL
- - …
lib
- libSDL2-2.0.so (dynamique) ou libSDL2.a (statique) (pour Linux)
- libSDL2.lib (pour Windows) ou libSDL2*.a (pour MinGW)
src
- main.c
SDL2.dll (pour Windows)
les fichiers de projets (.cbp (pour Code::Blocks), .sln (pour Visual Studio)…)

Lorsque vous voulez distribuer votre code, il suffit de compresser le dossier et c'est tout. L'utilisateur à qui vous donnez votre projet n'aura qu'à décompresser le fichier et il pourra directement l'utiliser.

IV-B. Premier code SDL 2

Afin de tester le bon fonctionnement de votre installation, vous devez créer une application SDL 2 minimaliste. Le code suivant ouvre une fenêtre pendant trois secondes et quitte :

Sélectionnez
#include <SDL2/SDL.h>

#include <stdio.h>

int main(int argc, char** argv)
{
    /* Initialisation simple */
    if (SDL_Init(SDL_INIT_VIDEO) != 0 )
    {
        fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError());
        return -1;
    }

    {
        /* Création de la fenêtre */
        SDL_Window* pWindow = NULL;
        pWindow = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED,
                                                                  SDL_WINDOWPOS_UNDEFINED,
                                                                  640,
                                                                  480,
                                                                  SDL_WINDOW_SHOWN);

        if( pWindow )
        {
            SDL_Delay(3000); /* Attendre trois secondes, que l'utilisateur voit la fenêtre */

            SDL_DestroyWindow(pWindow);
        }
        else
        {
            fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError());
        }
    }

    SDL_Quit();

    return 0;
}

IV-C. Configuration du projet

Une fois que tous les fichiers sont prêts, que le code source est prêt, il ne reste plus qu'à créer le projet de votre nouvelle application SDL 2 afin de la compiler.

Chaque éditeur de développement intégré possède ses propres menus, mais l'idée est toujours la même.

Nous devons spécifier le répertoire contenant les fichiers d'entête (include), le répertoire des fichiers de la bibliothèque (lib) et les bibliothèques à intégrer. Ne pouvant pas décrire ces étapes pour tous les éditeurs existants, seuls les plus connus seront présentés.

IV-C-1. GCC

Dans votre ligne de compilation, il suffit de spécifier le répertoire où chercher la bibliothèque (-L./lib), le répertoire des entêtes (-I./include) et d'indiquer que vous voulez utiliser la SDL 2.0 (-lSDL2-2.0).

Ce qui donne :

gcc -Wall -Wextra -L./lib -I./include -lSDL2-2.0 src/main.c -o main

Lorsque la bibliothèque est installée à l'aide du gestionnaire de paquets, ou de la commande make install, il n'est pas nécessaire de spécifier les dossiers lib et include car les fichiers sont dans les dossiers que le compilateur scanne par défaut.

IV-C-2. Code::Blocks

Créez un nouveau projet de type « Application console » (« Console application »).

Ajoutez le fichier main.c du dossier src dans votre projet (le fichier main.c créé par défaut est à retirer du projet et à supprimer).

Allez dans les options de compilation (« Build options »). Vérifiez que vous avez bien sélectionné la globalité du projet dans l'arborescence à gauche et non seulement une des cibles « Debug » ou « Release » afin d'appliquer vos modifications à toutes les cibles. Dans l'onglet « Répertoires de recherche » (« Search directories ») et le sous-onglet « Compilateur » (« Compiler »), ajoutez le répertoire ./include de votre projet afin que le compilateur trouve les fichiers d'entête. Dans le sous-onglet « Éditeur de liens » (« Linker »), ajoutez de la même manière le répertoire ./lib pour que les fichiers de la bibliothèque puissent être trouvés.

Dans l'onglet « Paramètres de compilation » (« Compiler Settings ») est le sous-onglet « Options de compilation » (« Compileur Flags »), vous pouvez cocher l'option « Activer les avertissements supplémentaires du compilateur [-Wextra] » (« Enable extra compiler warnings [-Wextra] ») afin que le compilateur vous renvoie le maximum d'informations sur votre code et ses possibles erreurs.

Finalement, dans l'onglet « Paramètres de l'éditeur de liens » (« Linker settings »), vous devez rajouter (dans cet ordre) :

libmingw32.a
libSDL2main.a
libSDL2.a
libSDL2.dll.a

Comme le montre cette capture d'écran :

Image non disponible

Afin que vous puissiez copier votre projet d'un répertoire à un autre et que Code::Blocks continue de trouver les fichiers, il est recommandé d'utiliser des chemins relatifs (par exemple : ./lib et ./include).

Vous devriez maintenant pouvoir compiler le projet.

IV-C-3. Microsoft Visual Studio

Créez un nouveau projet « Application Console Win32 » (« Win32 Console Application »). Dans le gestionnaire, indiquez que vous voulez un projet vide (« Empty project »).

Ajoutez le fichier main.c dans le projet.

Dans les propriétés du projet « Projet » → « Propriétés Projet SDL 2 » (« Project » → « Projet SDL 2 Properties »), cliquez sur « Propriétés de la configuration » (« Configuration Properties ») afin de déplier l'arborescence et de déverrouiller les deux options en haut de la fenêtre. Sélectionnez la configuration « Toutes plateformes » (« All Configurations ») afin que vos modifications s'appliquent sur la cible « Debug » et la cible « Release ».

Dans le menu C/C++, il est conseillé de changer l'entrée « Niveau d'avertissement » (« Warning Level ») du niveau 3 au niveau 4 afin que le compilateur vous renvoie le maximum d'informations sur votre code et ses possibles erreurs.

Dans le menu C/C++, rajoutez ./include pour l'entrée « Répertoires d'inclusion supplémentaires » (« Additional Include Directories »). Dans le menu « Éditeur de liens » (« Linker »), ajoutez ./lib pour l'entrée « Répertoires de bibliothèque supplémentaires » (« Additional Library Directories »). Dans le sous-menu « Entrée » (« Input »), pour l'entrée « Dépendances supplémentaires » (« Additional Dependencies »), insérez « SDL2.lib SDL2main.lib » :

Image non disponible

Pour valider chaque modification des propriétés, cliquez sur le bouton « Appliquer » (« Apply »).

Afin que vous puissiez copier votre projet d'un répertoire à un autre et que Visual Studio continue de trouver les fichiers, il est recommandé d'utiliser des chemins relatifs (par exemple : ./lib et ./include).

Vous devriez maintenant pouvoir compiler le projet.

IV-C-4. XCode▲

Créez un projet « outil en ligne de commande » (« Command Line Tool »). Cliquez sur votre projet dans l'arborescence se trouvant à gauche de l'éditeur afin de faire apparaître les propriétés. Ensuite cliquez sur « Paramètres de compilation » (« Build Settings ») et rajoutez /Libraries/Frameworks à l'entrée « Chemin de recherche des frameworks » (« Framework Search Paths ») permettant ainsi à Xcode de trouver le fichier Framework de la SDL. Dans l'onglet « Étapes de compilation » (« Build Phases ») et dans la sous-section « Lier binaire avec des bibliothèques » (« Link Binary With Libraries »), ajoutez le « Cocoa.framework », puis cliquez sur le bouton « Ajouter autre … » (« Add Other… ») puis dans le dossier /Library/Frameworks, trouvez SDL.framework. Votre projet est maintenant prêt à être compilé.

V. Projets prêts à utiliser

Afin que vous puissiez démarrer plus rapidement votre projet, voici des archives avec des projets préconfigurés pour différents éditeurs :

  • Windows - Code::Blocks (MinGW) (32 bits) ;
  • Windows - Microsoft Visual Studio 2008 (32 bits) ;
  • Linux - Makefile (64 bits).

Si vous voulez créer une application Windows 64 bits, vous pouvez remplacer les fichiers du dossier lib par leur équivalent 64 bits ainsi que le fichier SDL.dll.

Pour Linux, il est conseillé de recompiler les fichiers du dossier lib ou d'utiliser ceux installés par le gestionnaire de paquets.

VI. Problèmes

VI-A. Linux

VI-A-1. « Failed to load driver : swrast »

Ce message arrive lorsqu'il n'est pas possible à l'application de charger le pilote graphique. Il est conseillé de réinstaller les pilotes graphiques.

VI-A-2. « error while loading shared libraries: libSDL2-2.0.so.0: cannot open shared object file: No such file or directory »▲

Si vous n'avez pas installé la SDL avec make install, il suffit d'indiquer explicitement le chemin vers le .so de la SDL en lançant votre application :

LD_LIBRARY_PATH=./lib ./main

VI-B. Windows

VI-B-1. « number of arguments doesn't match prototype »

Avec la version MinGW de la SDL, il est obligatoire de déclarer son main de la forme :

Sélectionnez
int main(int argc, char** argv)

L'erreur apparaît si votre main est de la forme suivante :

Sélectionnez
int main()

« previous declaration of 'SDL_main' was here| »

Avec la version MinGW de la SDL, il est obligatoire de déclarer son main de la forme :

Sélectionnez
int main(int argc, char** argv)

L'erreur apparaît si votre main est de la forme suivante :

Sélectionnez
int main (void)

VI-B-3. « undefined reference to `WinMain@16' »

Pour compiler un programme avec MinGW, il est nécessaire de spécifier la bibliothèque « libmingw32.a » dans l'éditeur de liens, sinon l'erreur « undefined reference to 'WinMain@16' » apparaît.

VI-B-4. Échec de l'étape de configuration avec l'interface graphique de CMake

L'étape de configuration du projet peut échouer lorsque le dossier des sources possède des caractères accentués. Dans un tel cas, il suffit de recommencer la procédure après déplacement du dossier des sources de la SDL.

VI-B-5. Erreur 0xc000007b

Vous êtes sous Windows et votre projet compile correctement. Il se lance, mais avant de faire quoi que ce soit, il vous affiche l'erreur ayant pour obscur code 0xc000007b. Cela arrive dans le cas d'une incompatibilité entre la DLL que vous utilisez et l'architecture pour laquelle votre programme a été compilé. En effet, SDL 2 propose des fichiers en 32 et en 64 bits. Si vous tentez de faire fonctionner le programme avec la bibliothèque en 64 bits alors que vous utilisez un compilateur 32 bits (comme cela est le cas avec CodeBlocks (MinGW), par défaut) produisant des programmes en 32 bits, alors vous obtiendrez l'erreur. Pour la corriger, il suffit de remplacer les fichiers de la bibliothèque (.DLL, .lib…) par les fichiers en 32 bits et tout rentrera dans l'ordre.


Tutoriel SDL 2

Vos premières fenêtres SDL2

Table des matières

  • Navigation
  • II. Introduction
  • III. Initialisation de la bibliothèque
    • III-A. Le paramètre de SDL_Init()
    • III-B. La valeur de retour de SDL_Init()
  • IV. Une simple fenêtre
    • IV-A. Les paramètres de SDL_CreateWindow()
      • IV-A-1. Les options de création de la fenêtre
    • IV-B. La valeur de retour de SDL_CreateWindow()
  • V. Destruction d'une fenêtre
  • VI. Arrêter la SDL
  • VII. Plus de détails sur les fenêtres
    • VII-A. Fenêtre OpenGL (3D)
    • VII-B. Gestion de plusieurs fenêtres
    • VII-C. Interagir avec une fenêtre
  • VIII. La structure SDL_Window
  • IX. Les fenêtres dans les jeux vidéo
    • IX-A. Idées sur la gestion plein écran
      • IX-A-1. Les paramètres de SDL_SetWindowFullscreen
      • IX-A-2. La valeur de retour de SDL_SetWindowFullscreen
      • IX-A-3. Exemple
    • IX-B. Recherche des résolutions disponibles
      • IX-B-1. Les paramètres de SDL_GetNumDisplayMode()
      • IX-B-2. La valeur de retour de SDL_GetNumDisplayMode()
      • IX-B-3. Les paramètres de SDL_GetDisplayMode()
      • IX-B-4. La valeur de retour de SDL_GetDisplayMode()
      • IX-B-5. La structure SDL_DisplayMode
  • X. Remerciements
  • Navigation

Après avoir créé votre premier projet avec la bibliothèque SDL 2, l'étape logique faisant suite est la création d'une première fenêtre dans laquelle vous allez afficher votre jeu.

II. Introduction

Après avoir installé et configuré la SDL, vous pouvez maintenant commencer à programmer votre jeu. La première chose à effectuer pour utiliser la bibliothèque est de l'initialiser.Initialisation de la bibliothèque Ensuite, vous pourrez utiliser ses fonctions et, entre autres, ouvrir une fenêtre pour votre applicationUne simple fenêtre.

Autant que possible, ce tutoriel explique l'utilisation des fonctions. Si vous avez un doute, ou que le tutoriel n'est pas clair sur un point, vous pouvez vous référer à la documentation officielle. Si cela ne suffit pas, n'hésitez pas à demander sur le forum.

III. Initialisation de la bibliothèque

Le code du tutoriel précédent, copié ci-dessous, initialisait et ouvrait déjà une fenêtre avec la bibliothèque. Nous allons simplement maintenant l'analyser.

Sélectionnez
#include <SDL2/SDL.h>

#include <stdio.h>

int main(int argc, char** argv)
{
    /* Initialisation simple */
    if (SDL_Init(SDL_INIT_VIDEO) != 0 )
    {
        fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError());
        return -1;
    }

    {
        /* Création de la fenêtre */
        SDL_Window* pWindow = NULL;
        pWindow = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED,
                                                                  SDL_WINDOWPOS_UNDEFINED,
                                                                  640,
                                                                  480,
                                                                  SDL_WINDOW_SHOWN);

        if( pWindow )
        {
            SDL_Delay(3000); /* Attendre trois secondes, que l'utilisateur voie la fenêtre */

            SDL_DestroyWindow(pWindow);
        }
        else
        {
            fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError());
        }
    }

    SDL_Quit();

    return 0;
}

La section permettant l'initialisation de la bibliothèque est :

Sélectionnez
if (SDL_Init(SDL_INIT_VIDEO) != 0 )
{
    fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError());
    return -1;
}

Vous remarquerez donc l'utilisation de la fonction SDL_Init() permettant de « démarrer » la bibliothèque.

Afin de démarquer les fonctions de la bibliothèque SDL de vos fonctions ou des fonctions de la bibliothèque C standard, les développeurs de la bibliothèque ont nommé toutes les fonctions de la SDL en commençant par SDL_.

III-A. Le paramètre de SDL_Init()

L'unique paramètre de la fonction permet de définir les composants que la bibliothèque doit initialiser. Ceux-ci peuvent être :

SDL_INIT_TIMER | Initialise le sous-système des chronomètres
SDL_INIT_AUDIO | Initialise le sous-système pour l'audio
SDL_INIT_VIDEO | Initialise le sous-système pour le rendu
SDL_INIT_JOYSTICK | Initialise le sous-système pour les joysticks
SDL_INIT_HAPTIC | Initialise le sous-système pour le retour de force
SDL_INIT_GAMECONTROLLER | Initialise le sous-système pour les contrôleurs de jeux
SDL_INIT_EVENTS | Initialise le sous-système pour les événements
SDL_INIT_EVERYTHING | Initialise tous les sous-systèmes nommés ci-dessus.
SDL_INIT_NOPARACHUTE | Avec cette option, la SDL ne mettra pas en place de fonction de nettoyage lors de la réception de signaux fatals (par exemple : erreur de segmentation).

III-B. La valeur de retour de SDL_Init()

Dans le code mis en avant ci-dessus, la valeur de retour de la fonction est vérifiée afin de quitter le programme lors d'un cas d'erreur. Lorsqu'une erreur se produit, la valeur est différente de 0. Afin d'informer l'utilisateur (et, bien souvent le programmeur), un message clair est affiché sur la sortie standard d'erreur. La fonction SDL_GetError() permet de récupérer un descriptif de l'erreur rencontrée par la bibliothèque.

Il est conseillé de vérifier les valeurs de retour de chaque fonction pouvant rencontrer des erreurs (pas uniquement lors de l'utilisation de la SDL) afin de réagir au mieux par rapport à ces cas qui arrivent plus souvent que l'on ne le croit. De plus, cela vous aidera en tant que programmeur afin de trouver les bogues plus rapidement.

IV. Une simple fenêtre

Une fois que la bibliothèque est initialisée, vous pouvez l'utiliser et donc, ouvrir une fenêtre.

Le code du programme ouvrant une fenêtre dans le précédent tutoriel était :

Sélectionnez
SDL_Window* pWindow = NULL;
pWindow = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED,
                                                          SDL_WINDOWPOS_UNDEFINED,
                                                          640,
                                                          480,
                                                          SDL_WINDOW_SHOWN);

if( pWindow )
{
    /* Suite du programme */

    SDL_DestroyWindow(pWindow); // Destruction de la fenêtre
}
else
{
        fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError());
}

La fonction créant une fenêtre est : SDL_CreateWindow(). Celle-ci retourne un pointeur sur une variable de type SDL_Window (nouvelle structure implémentée à partir de SDL 2).

IV-A. Les paramètres de SDL_CreateWindow()

SDL_CreateWindow() attend plusieurs paramètres afin de créer la fenêtre. Ceux-ci sont :

  • le nom à afficher ;
  • la position en X ;
  • la position en Y ;
  • la taille en largeur ;
  • la taille en hauteur ;
  • les options (plein écran, fenêtre OpenGLFenêtre OpenGL (3D)…).

Pour la position, vous pouvez utiliser SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED, deux valeurs spéciales indiquant que c'est à la bibliothèque de déterminer la position de la fenêtre. La première valeur laisse le libre champ à la bibliothèque de placer la fenêtre où elle le souhaite, la seconde permet de toujours avoir une fenêtre centrée sur l'écran.

IV-A-1. Les options de création de la fenêtre

Le dernier paramètre de la fonction SDL_CreateWindow() permet d'indiquer des options spécifiques pour la fenêtre à créer. Celles-ci sont :

SDL_WINDOW_FULLSCREEN | Crée une fenêtre plein écran
SDL_WINDOW_FULLSCREEN_DESKTOP | Crée une fenêtre plein écran à la résolution actuelle du bureau
SDL_WINDOW_OPENGL | Crée une fenêtre pouvant être utilisée pour OpenGL
SDL_WINDOW_SHOWN | Crée une fenêtre et l'afficher
SDL_WINDOW_HIDDEN | Crée une fenêtre cachée
SDL_WINDOW_BORDERLESS | Crée une fenêtre sans bordure
SDL_WINDOW_RESIZABLE | Crée une fenêtre redimensionnable
SDL_WINDOW_MINIMIZED | Crée une fenêtre minimisée
SDL_WINDOW_MAXIMIZED | Crée une fenêtre maximisée
SDL_WINDOW_INPUT_GRABBED | Crée une fenêtre et récupère le focus d'entrée
SDL_WINDOW_INPUT_FOCUS | Crée une fenêtre et donne le focus d'entrée
SDL_WINDOW_MOUSE_FOCUS | Crée une fenêtre et donne le focus de la souris
SDL_WINDOW_FOREIGN | La fenêtre n'est pas créée par la SDL

IV-B. La valeur de retour de SDL_CreateWindow()

La fonction SDL_CreateWindow() renvoie un pointeur sur une variable de type SDL_WindowLa structure SDL_Window, représentant la fenêtre. Chaque fois que vous devez agir sur cette fenêtre, vous devez indiquer la variable retournée.

En cas d'erreur, la variable retournée aura la valeur NULL. Comme pour l'initialisation de la bibliothèqueInitialisation de la bibliothèque, vous pouvez utiliser SDL_GetError() pour avoir une chaîne de caractères décrivant l'erreur.

Image non disponible Encore une fois, il est fortement conseillé de vérifier la valeur retournée par la fonction. En effet, l'utilisation d'un pointeur NULL entrainera des erreurs de segmentation (« segmentation fault »).

V. Destruction d'une fenêtre

Comme nous avons pu le voir durant la création d'une fenêtreUne simple fenêtre avec la fonction SDL_CreateWindow(), une fenêtre est représentée par une variable de type SDL_Window. Afin de détruire une fenêtre précédemment créée, vous devez passer la variable la représentant à la fonction : SDL_DestroyWindow(), comme suit :

Sélectionnez
SDL_DestroyWindow(pWindow);

VI. Arrêter la SDL

À la fin du programme (ou, lorsque vous n'avez plus besoin de la SDL), il est nécessaire de faire l'opération inverse à l'initialisation avec la fonction : SDL_Quit(). (Ceci est aussi vrai, si vous rencontrez une erreur lors de l'exécution du programme.)

La fonction SDL_Quit() ne prend pas de paramètre et ne retourne pas de valeur, il suffit de l'appeler de la sorte :

Sélectionnez
SDL_Quit();

VII. Plus de détails sur les fenêtres

Maintenant que vous avez vu la création d'une fenêtre avec la SDL 2, vous pouvez maintenant vous intéresser aux détails concernant sa gestion. En effet, la bibliothèque vous permet de créer une fenêtre compatible avec un rendu effectué par OpenGLFenêtre OpenGL (3D), de créer plusieurs fenêtresGestion de plusieurs fenêtres ou même d'interagir sur une fenêtreInteragir avec une fenêtre.

VII-A. Fenêtre OpenGL (3D)

Une fenêtre OpenGL se construit de la même manière qu'une fenêtre normale : avec la fonction SDL_CreateWindow(). Toutefois, pour que OpenGL puisse dessiner dedans, il faut obligatoirement spécifier l'option SDL_WINDOW_OPENGL lors de la création.

VII-B. Gestion de plusieurs fenêtres

Depuis la SDL 2, il est possible d'afficher et de gérer plusieurs fenêtres SDL. Cela est possible grâce à l'introduction de la structure SDL_Window, représentant une fenêtre. En effet, chaque appel à SDL_CreateWindow() retournera un pointeur sur une variable SDL_Window différente, permettant ainsi de les identifier par la bibliothèque. Lorsque vous souhaitez agir sur telle ou telle fenêtre, il suffira d'utiliser la variable correspondante.

VII-C. Interagir avec une fenêtre

De multiples fonctions sont disponibles dans la bibliothèque afin d'interagir avec la bibliothèque. Voici un récapitulatif :

SDL_CreateWindow() | Crée une fenêtre
SDL_CreateWindowFrom() | Crée une fenêtre SDL à partir d'une fenêtre déjà existante
SDL_DestroyWindow() | Détruit une fenêtre
SDL_GetWindowData() | Récupère les données utilisateur associées à la fenêtre
SDL_GetWindowFlags() | Récupère les options actuelles de la fenêtre
SDL_GetWindowGrab() | Détermine si la fenêtre a le focus clavier
SDL_GetWindowPosition() | Récupère la position actuelle de la fenêtre
SDL_GetWindowSize() | Récupère la taille actuelle de la fenêtre
SDL_GetWindowTitle() | Récupère le titre actuel de la fenêtre
SDL_HideWindow() | Cache la fenêtre
SDL_MaximizeWindow() | Agrandit la fenêtre
SDL_MinimizeWindow() | Réduit la fenêtre dans la barre des tâches
SDL_RaiseWindow() | Place la fenêtre devant les autres
SDL_RestoreWindow() | Restaure la taille et la position d'une fenêtre minimisée ou maximisée
SDL_SetWindowData() | (Re)Définit les données utilisateur de la fenêtre
SDL_SetWindowFullscreen() | Passe la fenêtre en plein écran
SDL_SetWindowGrab() | (Re)Donne le focus clavier à cette fenêtre
SDL_SetWindowIcon() | (Re)Définit l'icône de la fenêtre
SDL_SetWindowPosition() | (Re)Définit la position de la fenêtre
SDL_SetWindowSize() | (Re)Définit la taille de la fenêtre
SDL_SetWindowBordered() | (Re)Définit l'affichage des bordures de la fenêtre
SDL_SetWindowTitle() | (Re)Définit le titre de la fenêtre
SDL_ShowWindow() | Affiche la fenêtre

VIII. La structure SDL_Window

Il n'est certes pas essentiel de connaître le contenu de cette structure, toutefois, cela peut être intéressant et didactique de le savoir. En sachant de telles choses, vous pouvez comprendre plus facilement comment a été construite la SDL et ainsi mieux comprendre son fonctionnement.

La structure est définie dans le fichier d'entête SDL_video.h. Toutefois la seule ligne intéressante de ce fichier est :

Sélectionnez
typedef struct SDL_Window SDL_Window;

La ligne définit bien le type SDL_Window, mais ne donne pas d'information sur son contenu. En effet, le programmeur utilisant la bibliothèque n'a pas à avoir accès à ses champs (afin d'éviter de mauvais usages) et de cette façon, il n'y a pas accès. Pour en savoir plus, il faut explorer le code source de la bibliothèque. On trouvera dans le fichier SDL_sysvideo.h la définition suivante :

Sélectionnez
/* Définit la structure de fenêtre SDL, correspondant aux fenêtres de haut niveau */
struct SDL_Window
{
    const void *magic;
    Uint32 id;
    char *title;
    SDL_Surface *icon;
    int x, y;
    int w, h;
    int min_w, min_h;
    int max_w, max_h;
    Uint32 flags;

    /* Conserve la position et la taille d'une fenêtre non plein écran */
    SDL_Rect windowed;

    SDL_DisplayMode fullscreen_mode;

    float brightness;
    Uint16 *gamma;
    Uint16 *saved_gamma;        /* (un simple décalage du gamma) */

    SDL_Surface *surface;
    SDL_bool surface_valid;

    SDL_WindowShaper *shaper;

    SDL_WindowUserData *data;

    void *driverdata;

    SDL_Window *prev;
    SDL_Window *next;
};

Certains pourraient avoir deviné quelque peu son contenu. En effet, il semble logique que cette structure conserve la taille de la fenêtre, le titre et le mode (plein-écran ou non), car ce sont les paramètres que nous donnons lors de la création de celle-ciUne simple fenêtre. On remarquera aussi l'utilisation de SDL_Surface (une structure déjà présente dans SDL 1.X) pour conserver l'icône (icon) ainsi que le contenu de la fenêtre. Plus étonnant, la structure garde aussi un pointeur sur la fenêtre précédemment créée (prev) et la fenêtre suivante (si une seconde fenêtre est créée) (next). On devinera alors que la structure SDL_Window est aussi un maillon d'une liste doublement chainée.

IX. Les fenêtres dans les jeux vidéo

Maintenant que vous savez utiliser les fenêtres avec la SDL 2, il est important de revenir sur quelques aspects de la gestion des fenêtres pour les jeux vidéo. En effet, la fenêtre est le cadre où apparaitra votre jeu. Chaque joueur a ses propres préférences de jeu, certains aiment jouer dans une petite fenêtre, d'autres en plein écran. Pour cela, vous allez devoir mettre en place un mode plein écranIdées sur la gestion plein écran ou encore, détecter les résolutionsRecherche des résolutions disponibles utilisables sur la machine.

IX-A. Idées sur la gestion plein écran

Afin de fournir un jeu ou une application qui ne contraint pas l'utilisateur et ne dérange pas ses habitudes (ce qui pourrait entrainer un mécontentement), il est conseillé d'éviter l'imposition du mode plein écran. Toutefois, le proposer en option (par exemple, au démarrage de l'application) est une bonne idée.

Vous pouvez aussi opter pour la mise en place d'une touche permettant de passer à la volée du mode fenêtré au mode plein écran et inversement. Pour cela, vous devrez utiliser la fonction : SDL_SetWindowFullscreen().

IX-A-1. Les paramètres de SDL_SetWindowFullscreen

La fonction accepte deux paramètres :

  • la fenêtre sur laquelle vous voulez agir ;
  • le nouvel état de la fenêtre parmi :
    • 0, pour remettre la fenêtre en mode fenêtré ;
    • SDL_WINDOW_FULLSCREEN, pour mettre la fenêtre en plein écran ;
    • SDL_WINDOW_FULLSCREEN_DESKTOP, pour mettre la fenêtre en plein écran à la résolution du bureau.

IX-A-2. La valeur de retour de SDL_SetWindowFullscreen

La fonction retourne 0 si le changement a été effectué, ou une valeur négative dans le cas contraire. En cas d'erreur, vous pourrez récupérer un message précis sur la cause avec la fonction SDL_GetError().

IX-A-3. Exemple

Sélectionnez
SDL_Event event;
while (SDL_PollEvent(&event)) // Récupération des actions de l'utilisateur
{
    switch(event.type)
    {
        case SDL_QUIT: // Clic sur la croix
            quit=1;
            break;
        case SDL_KEYUP: // Relâchement d'une touche
            if ( event.key.keysym.sym == SDLK_f ) // Touche f
            {
                // Alterne du mode plein écran au mode fenêtré
                if ( fullscreen == 0 )
                {
                    fullscreen = 1;
                    SDL_SetWindowFullscreen(pWindow,SDL_WINDOW_FULLSCREEN);
                }
                else if ( fullscreen == 1 )
                {
                    fullscreen = 0;
                    SDL_SetWindowFullscreen(pWindow,0);
                }
            }
            break;
    }
}

La gestion des événements sera analysée dans un prochain tutoriel.

Dans une application complète, il est déconseillé de mettre un tel code directement dans la section gérant les événements. En effet, faire de la sorte et pour tous les événements mène à un code difficile à relire et à maintenir.

IX-B. Recherche des résolutions disponibles

Dans une optique proche de l'option pour le mode fenêtré/plein écran, il est intéressant pour l'utilisateur d'avoir le choix de la résolution de l'application. Vous devrez donc rechercher les résolutions disponibles sur sa machine et lui proposer de choisir parmi l'une d'elles. Le code suivant permet de détecter les résolutions utilisables :

Sélectionnez
// On récupère le nombre de modes d'affichage pour l'écran 0
modeNumber = SDL_GetNumDisplayModes(0);
if ( modeNumber < 0 )
{
    fprintf(stdout,"Échec lors de la récupération du nombre de modes (%s)\n",SDL_GetError());
    return -2;
}
fprintf(stdout,"Vous avez %d mode(s) d'affichage\n",modeNumber);

// Affichage des différents modes
for ( i = 0 ; i < modeNumber ; i++ )
{
    error = SDL_GetDisplayMode(0,i,&displayMode);
    if ( error < 0 )
    {
        fprintf(stdout,"Échec lors de la récupération du mode d'affichage (%s)\n",SDL_GetError());
        return -3;
    }

    fprintf(stdout,"Mode %d : %dx%dx%d\n",modeNumber,displayMode.w,displayMode.h,displayMode.refresh_rate);
}

Ce code doit être inséré après l'initialisation de la SDLInitialisation de la bibliothèque (et avant son arrêtArrêter la SDL).

Pour afficher les résolutions disponibles, il est nécessaire d'utiliser les fonctions SDL_GetNumDisplayMode() et SDL_GetDisplayMode(). La première retourne le nombre de modes d'affichage disponibles, la seconde, remplit une structure avec les informations du mode d'affichage.

IX-B-1. Les paramètres de SDL_GetNumDisplayMode()

La fonction SDL_GetNumDisplayMode() accepte un unique paramètre qui est l'index de l'écran. Vous pouvez mettre 0 par défaut (car vous avez normalement, toujours un écran), faisant que vous récupérerez les informations pour le premier écran.

La fonction SDL_GetNumVideoDisplays() vous permet de connaître le nombre d'écrans présents sur votre configuration.

IX-B-2. La valeur de retour de SDL_GetNumDisplayMode()

La fonction retourne le nombre de modes d'affichage pour l'écran indiqué. Si le nombre est négatif, c'est qu'une erreur s'est produite.

IX-B-3. Les paramètres de SDL_GetDisplayMode()

La fonction accepte trois paramètres :

  • l'index de l'écran ;
  • l'index du mode d'affichage à récupérer ;
  • un pointeur sur la structure SDL_DisplayMode qui sera remplie avec les informations voulues.

IX-B-4. La valeur de retour de SDL_GetDisplayMode()

Si la valeur est négative, une erreur s'est produite. Si elle est de zéro, tout va bien et vous devriez pouvoir lire les informations du mode d'affichage dans la variable de type SDL_DisplayMode.

IX-B-5. La structure SDL_DisplayMode

Celle-ci est constituée de cinq champs :

  • Uint32 format : le format des pixels pour ce mode d'affichage (voir SDL_PixelFormatEnum) ;
  • int w : la largeur ;
  • int h : la hauteur ;
  • int refresh_rate : le taux de rafraichissement (ou 0 si non spécifié) ;
  • void* driverdata : des données spécifiques au pilote.


Tutoriel SDL 2

Afficher son premier sprite avec la SDL 2.0

Table des matières

  • Navigation
  • I. Introduction
  • II. Méthode SDL 1.2
    • II-A. Type de données utiles
      • II-A-1. SDL_Surface
      • II-A-2. SDL_Rect
    • II-B. Chargement Sprite
      • II-B-1. Les paramètres de SDL_LoadBMP()
      • II-B-2. La valeur de retour de SDL_LoadBMP()
    • II-C. Affichage du sprite
      • II-C-1. Définition de la destination du sprite
      • II-C-2. Récupération de la surface destination
        • II-C-2-a. Les paramètres de SDL_GetWindowSurface()
        • II-C-2-b. La valeur de retour de SDL_GetWindowSurface()
      • II-C-3. Copie du sprite dans la fenêtre
        • II-C-3-a. Les paramètres de SDL_BlitSurface()
        • II-C-3-b. La valeur de retour de SDL_BlitSurface()
      • II-C-4. Affichage
        • II-C-4-a. Le paramètres de SDL_UpdateWindowSurface()
        • II-C-4-b. La valeur de retour de SDL_UpdateWindowSurface()
      • II-C-5. Libération des ressources liées au sprite
        • II-C-5-a. Le paramètre de SDL_FreeSurface()
      • II-C-6. Récapitulatif
  • III. Méthode SDL 2.0
    • III-A. Type de données utiles
      • III-A-1. SDL_Renderer
      • III-A-2. SDL_Texture
    • III-B. Création d'un SDL_Renderer
      • III-B-1. Les paramètres de SDL_CreateRenderer()
        • III-B-1-a. Options du SDL_Renderer
      • III-B-2. La valeur de retour de SDL_CreateRenderer()
    • III-C. Créer la SDL_Texture à partir d'une SDL_Surface
      • III-C-1. Les paramètres de SDL_CreateTextureFromSurface()
      • III-C-2. La valeur de retour de SDL_CreateTextureFromSurface()
    • III-D. Récupération de la taille de la texture
      • III-D-1. Les paramètres de SDL_QueryTexture()
      • III-D-2. La valeur de retour de SDL_QueryTexture()
    • III-E. Copie du sprite
      • III-E-1. Les paramètres de SDL_RenderCopy()
      • III-E-2. La valeur de retour de SDL_RenderCopy()
    • III-F. Affichage
      • III-F-1. Le paramètre de SDL_RenderPresent()
    • III-G. Libération de la mémoire liée à la SDL_Texture
      • III-G-1. Le paramètre de SDL_DestroyTexture()
    • III-H. Libération de la mémoire liée au SDL_Renderer
      • III-H-1. Le paramètre de SDL_DestroyRenderer()
    • III-I. Récapitulatif
  • IV. Nettoyage de l'écran
    • IV-A. SDL 1.X
      • IV-A-1. Les paramètres de SDL_FillRect()
      • IV-A-2. La valeur de retour de SDL_FillRect()
    • IV-B. SDL 2
      • IV-B-1. Sélection de la couleur de nettoyage
        • IV-B-1-a. Les paramètres de SDL_SetRenderDrawColor()
      • IV-B-2. La valeur de retour de SDL_SetRenderDrawColor()
      • IV-B-3. Nettoyage
        • IV-B-3-a. Le paramètre de SDL_RenderClear()
        • IV-B-3-b. La valeur de retour de SDL_RenderClear()
  • V. Remerciements
  • Navigation

Maintenant que vous avez ouvert une fenêtre, vous pouvez l'utiliser pour afficher des sprites.

I. Introduction

Vous savez créer un projet avec la SDL 2, initialiser la bibliothèque, ouvrir une fenêtre, vous pouvez maintenant remplir le contenu de la fenêtre en affichant un premier sprite.

Un sprite est une image (un tableau contenant des couleurs), pouvant être généré à la volée par le programme, ou plus simplement, chargé à partir d'un fichier et qui sera copié une ou plusieurs fois dans la fenêtre de destination à un emplacement voulu.

II. Méthode SDL 1.2

L'affichage des sprites comme cela était fait avec la SDL 1.2 est encore d'actualité dans cette nouvelle version de la bibliothèque. Toutefois, il faut bien comprendre que celle-ci n'est pas accélérée par le matériel et sera donc plus lente à exécuter. En contrepartie, cette méthode est simple à mettre en place et est la base pour la méthode utilisant l'accélération graphique avec la SDL 2.0.

II-A. Type de données utiles

Avant de commencer à voir les fonctions dont vous avez besoin pour afficher un sprite sur l'écran, il est nécessaire de connaître les types de données utilisés par ces fonctions.

II-A-1. SDL_Surface

SDL_Surface est une structure utilisée pour contenir un ensemble de pixels et les informations nécessaires pour utiliser ces pixels. Voici la liste des champs de la structure :

Type | Nom | Description
SDL_PixelFormat* | format | Le format des pixels stockés dans la surface.
int | w | La largeur en pixels de la surface (lecture seule).
int | h | La hauteur en pixels de la surface (lecture seule).
int | pitch | La longueur d'une ligne de pixels en octets (lecture seule).
void* | pixels | Le pointeur sur les pixels de la surface.
void* | userdata | Un pointeur sur des données utilisateur.
SDL_Rect | clip_rect | Un rectangle de découpage des opérations de blits sur cette surface. Vous devez le définir avec la fonction SDL_SetClipRect() (lecture seule).
int | refcount | Un compteur de référence pouvant être incrémenté par l'application.

D'autres champs sont aussi accessibles, mais ils sont internes au fonctionnement de la SDL. Ainsi, il est déconseillé de tenter de les utiliser tels quels et ne doivent être accédés qu'en utilisant des fonctions mises en place pour cela.

Il en est de même pour les champs indiqués en lecture seule : vous ne devez en aucun cas les modifier.

II-A-2. SDL_Rect

SDL_Rect représente un rectangle. Pour cela, quatre champs sont définis :

Type | Nom | Description
int | x | La position du coin supérieur gauche du rectangle sur l'axe des X.
int | y | La position du coin supérieur gauche du rectangle sur l'axe des Y.
int | w | La largeur du rectangle.
int | h | La hauteur du rectangle.

Il est possible de définir un rectangle en spécifiant son coin supérieur gauche et son coin inférieur droit, mais, sachant que les informations de largeur et de hauteur sont très utilisées, il est préférable de ne pas les calculer à chaque utilisation.

II-B. Chargement Sprite

Le sprite doit être lu à partir d'un fichier image sur le disque dur (ou à partir d'une zone mémoire). Pour cela, la SDL propose la fonction SDL_LoadBMP() afin de charger le fichier (ou SDL_Load_RW() ou encore SDL_CreateRGBSurfaceFrom() pour lire une zone mémoire).

Comme le nom de la fonction l'indique, elle ne chargera que des fichiers BMP. Pour pouvoir lire d'autres formats (GIF, PNG, JPG…), vous pouvez utiliser la bibliothèque complémentaire à la SDL : SDL_image.

II-B-1. Les paramètres de SDL_LoadBMP()

La fonction SDL_LoadBMP() charge une surface à partir d'un fichier BMP, ainsi elle prend en paramètre une chaine de caractères : chemin menant au fichier BMP à charger.

II-B-2. La valeur de retour de SDL_LoadBMP()

Lorsque le chargement est réussi, la fonction SDL_LoadBMP() retourne un pointeur sur SDL_SurfaceSDL_Surface, sinon, la fonction retourne NULL. Vous pouvez connaître la raison avec SDL_GetError().

II-C. Affichage du sprite

Une fois le sprite chargé et donc que vous avez une SDL_SurfaceSDL_Surface lui correspondant, vous pouvez maintenant copier votre surface à l'écran. Pour cela, vous pouvez utiliser SDL_BlitSurface(), mais cela demande une petite préparation.

II-C-1. Définition de la destination du sprite

Vous devez définir l'endroit où votre sprite sera copié en utilisant la structure SDL_RectSDL_Rect :

Sélectionnez
SDL_Rect dest = { 640/2 - pSprite->w/2,480/2 - pSprite->h/2, 0, 0};

Les deux premiers champs de la structure SDL_Rect servent à indiquer où nous voulons copier le sprite (position de son coin supérieur gauche). Les deux derniers champs sont inutiles pour la fonction SDL_BlitSurface().

Ici, nous voulons centrer le sprite dans notre fenêtre de résolution 640 x 480. Pour ce faire, nous calculons le centre de la fenêtre : 640/2 , 480/2. Mais cela ne suffit pas, car le sprite est dessiné en partant de son coin supérieur gauche. Il est donc nécessaire de le décaler afin que son centre corresponde au centre de la fenêtre. Le centre du sprite s'obtient en divisant la largeur et la hauteur par deux (comme pour la fenêtre). Nous pouvons récupérer la largeur et la hauteur de la structure SDL_SurfaceSDL_Surface avec les champs 'w' et 'h' : pSprite->w/2, pSprite->h/2. Connaissant le centre du sprite, il est maintenant possible de nous décaler pour faire correspondre celui-ci avec le centre de l'image : centre_de_l'image - centre_du_sprite. Ce qui donne dans le code : 640/2 - pSprite->w/2,480/2 - pSprite->h/2.

Si vraiment vous n'y arrivez pas, prenez une feuille de papier, dessiner un rectangle assez grand pour votre fenêtre, un carré pour votre sprite et écrivez les positions x et y des points. Cela devrait devenir clair.

En place de « 640 » et « 480 », il serait judicieux de récupérer la taille de la fenêtre avec la fonction SDL_GetWindowSize(). Ainsi, le code resterait correct, quelle que soit la résolution.

II-C-2. Récupération de la surface destination

SDL_BlitSurface() nécessite un pointeur sur une instance de SDL_Surface comme destination de son opération de copie. Le sprite doit être affiché dans la fenêtre, mais celle-ci est de type SDL_Window. Il est possible de récupérer la SDL_Surface associée à votre fenêtre (SDL_Window) grâce à la fonction SDL_GetWindowSurface(). Comme nous l'avons vu dans le tutoriel précédent, la structure SDL_Window contient une SDL_Surface représentant les pixels de la fenêtre. Cette surface peut être récupérée comme suit :

Sélectionnez
SDL_Surface pWinSurf = SDL_GetWindowSurface(pWindow);

II-C-2-a. Les paramètres de SDL_GetWindowSurface()

La fonction SDL_GetWindowSurface() n'accepte qu'un seul paramètre, qui est un pointeur sur une SDL_Window, soit, la fenêtre sur laquelle vous voulez dessiner.

II-C-2-b. La valeur de retour de SDL_GetWindowSurface()

La fonction retourne un pointeur sur la SDL_Surface associée à la fenêtre passée en argument. Si une erreur se produit, la fonction retournera NULL et vous pourrez récupérer quelques détails supplémentaires avec SDL_GetError().

II-C-3. Copie du sprite dans la fenêtre

Vous avez tous les éléments pour copier votre sprite dans la fenêtre :

  • la surface source, chargée avec SDL_LoadBMP()Chargement Sprite ;
  • la surface de destination récupérée grâce à SDL_GetWindowSurface() ;
  • l'emplacement voulu pour le spriteDéfinition de la destination du sprite.

Il suffit d'appeler SDL_BlitSurface() avec les paramètres préparés :

Sélectionnez
SDL_BlitSurface(pSprite,NULL,SDL_GetWindowSurface(pWindow),&dest);

Vous remarquez que ce sont des pointeurs sur des SDL_Rect qui sont attendus par la fonction. Les variables peuvent donc être modifiées par la fonction et c'est le cas, lorsque vous affichez un sprite qui sort de l'écran. Le SDL_Rect résultant correspondra uniquement à la partie du sprite visible sur l'écran.

Par exemple, si vous essayez d'afficher un sprite qui sort de l'écran par la gauche, donc qui aura une coordonnée en X négative, la fonction vous retournera un X valant zéro et une largeur de sprite diminuée.

II-C-3-a. Les paramètres de SDL_BlitSurface()

SDL_BlitSurface() demande quatre paramètres :

  • la surface source à copier ;
  • la région source (un SDL_RectSDL_Rect représentant le sous-rectangle source à copier). Si ce paramètre est NULL, la globalité du sprite est copiée ;
  • la surface destination (où copier la surface source) ;
  • l'emplacement destination (même si un SDL_RectSDL_Rect est nécessaire, seuls les deux premiers paramètres, x et y, sont utilisés).

II-C-3-b. La valeur de retour de SDL_BlitSurface()

SDL_BlitSurface() retourne un entier valant zéro si aucune erreur ne s'est produite, ou un nombre négatif si un problème est apparu. Dans ce cas, vous pouvez récupérer l'erreur avec SDL_GetError().

II-C-4. Affichage

Arrivé ici, rien ne va apparaître à l'écran. En effet, SDL_BlitSurface() copiera le sprite sur la surface de l'écran, sauf que celle-ci est une zone mémoire et ne correspond pas à l'écran physique. Pour afficher cette surface à l'écran, il suffit d'appeler SDL_UpdateWindowSurface() qui forcera la mise à jour de la fenêtre et prendra donc en compte le changement de la surface :

Sélectionnez
SDL_UpdateWindowSurface(pWindow);

Voilà, vous pouvez maintenant voir le sprite à l'écran.

II-C-4-a. Le paramètres de SDL_UpdateWindowSurface()

La fonction prend qu'une instance de SDL_Window de la fenêtre à mettre à jour.

II-C-4-b. La valeur de retour de SDL_UpdateWindowSurface()

La fonction retourne zéro si tout se passe correctement ou un entier négatif en cas d'erreur. Vous pouvez appeler SDL_GetError() pour avoir plus d'information sur la source de l'erreur.

II-C-5. Libération des ressources liées au sprite

Tout comme pour la fenêtre, une fois que vous n'avez plus besoin du sprite, vous devez libérer la mémoire associée. La fonction pour ce faire est SDL_FreeSurface() :

Sélectionnez
SDL_FreeSurface(pSprite);

II-C-5-a. Le paramètre de SDL_FreeSurface()

SDL_FreeSurface() accepte simplement un pointeur sur la SDL_Surface à libérer.

II-C-6. Récapitulatif

Voici le code d'affichage d'un sprite :

Sélectionnez
SDL_Surface* pSprite = SDL_LoadBMP("./data/dvp.bmp");
if ( pSprite )
{
    SDL_Rect dest = { 640/2 - pSprite->w/2,480/2 - pSprite->h/2, 0, 0};
    SDL_BlitSurface(pSprite,NULL,SDL_GetWindowSurface(pWindow),&dest); // Copie du sprite

    SDL_UpdateWindowSurface(pWindow); // Mise à jour de la fenêtre pour prendre en compte la copie du sprite
    SDL_Delay(3000); /* Attendre trois secondes, que l'utilisateur voit la fenêtre */

    SDL_FreeSurface(pSprite); // Libération de la ressource occupée par le sprite
}
else
{
    fprintf(stdout,"Échec de chargement du sprite (%s)\n",SDL_GetError());
}

III. Méthode SDL 2.0

La version 2 de la bibliothèque apporte une nouvelle façon d'afficher un sprite. Cette nouvelle méthode permet à la bibliothèque d'optimiser le rendu et de le faire s'exécuter sur le GPU. Toutefois, même si elle apporte des nouveautés, les bases sont les mêmes que ce qui a été vu dans la méthode d'affichage avec la SDL 1.2. Ainsi, les structures seront réutilisées, ainsi que la méthode pour charger le sprite.

III-A. Type de données utiles

III-A-1. SDL_Renderer

La structure SDL_Renderer peut être assimilée à un pinceau. C'est grâce à SDL_Renderer que vous allez pouvoir afficher des éléments dans la fenêtre et cela, en utilisant la carte graphique, si vous le souhaitez.

III-A-2. SDL_Texture

La SDL_Texture peut être perçue comme une SDL_SurfaceSDL_Surface, mais placée dans la mémoire de la carte graphique (et donc, utilisable par celle-ci). Vous pouvez la créer à partir d'une SDL_Surface.

Pour rappel, la mémoire CPU (communément appelée mémoire RAM) et la mémoire GPU (appelée VRAM, 'V' pour « Video ») sont deux mémoires distinctes et non partagées. Un élément présent dans l'une des mémoires ne sera pas accessible dans la seconde. Pour rendre accessibles des données d'une mémoire à l'autre, une opération de copie est nécessaire.

III-B. Création d'un SDL_Renderer

L'instance de SDL_Renderer peut être créée à partir d'une fenêtre avec la fonction SDL_CreateRenderer() :

Sélectionnez
SDL_Renderer *pRenderer = SDL_CreateRenderer(pWindow,-1,SDL_RENDERER_ACCELERATED);

III-B-1. Les paramètres de SDL_CreateRenderer()

La fonction SDL_CreateRenderer() accepte trois paramètres :

  • la fenêtre (SDL_Window) pour laquelle le SDL_Renderer doit être créé ;
  • un index spécifiant le SDL_Renderer à initialiser ou -1 pour utiliser le premier supportant les options indiquées ;
  • des options sur le SDL_Renderer.

III-B-1-a. Options du SDL_Renderer

La SDL 2 propose quatre options lors de la création d'un SDL_Renderer :

SDL_RENDERER_SOFTWARE | Le SDL_Renderer est purement logiciel (solution de repli).
SDL_RENDERER_ACCELERATED | Le SDL_Renderer utilise l'accélération matérielle.
SDL_RENDERER_PRESENTVSYNC | Le SDL_Renderer est synchronisé au rafraichissement de la fenêtre.
SDL_RENDERER_TARGETTEXTURE | Le SDL_Renderer supporte le rendu vers une texture.

III-B-2. La valeur de retour de SDL_CreateRenderer()

SDL_CreateRenderer() retourne en toute logique un pointeur sur une instance de SDL_Renderer. En cas d'erreur la fonction retourne NULL et des informations sur celle-ci seront disponibles en appelant SDL_GetError().

III-C. Créer la SDL_Texture à partir d'une SDL_Surface

La fonction permettant de créer une SDL_Texture à partir d'une SDL_Surface est SDL_CreateTextureFromSurface() :

Sélectionnez
SDL_Texture* pTexture = SDL_CreateTextureFromSurface(pRenderer,pSprite);

Une fois la SDL_Texture créée, vous pouvez libérer la SDL_Surface avec SDL_FreeSurface()Libération des ressources liées au sprite. En effet, la fonction copie les données de SDL_Surface.

III-C-1. Les paramètres de SDL_CreateTextureFromSurface()

SDL_CreateTextureFromSurface() nécessite deux paramètres :

  • le SDL_Renderer pour lequel la texture est créée ;
  • la SDL_Surface sur laquelle se baser pour créer la SDL_Texture.

III-C-2. La valeur de retour de SDL_CreateTextureFromSurface()

La fonction retourne un pointeur sur la SDL_Texture nouvellement créée. Si une erreur est survenue, un pointeur NULL sera retourné et un message d'erreur explicite pourra être récupéré avec SDL_GetError().

III-D. Récupération de la taille de la texture

Pour la copie du sprite, nous avons besoin de la taille de la texture que nous utilisons. Pour récupérer des informations sur cette dernière, nous devons utiliser SDL_QueryTexture(). Son utilisation est la suivante :

Sélectionnez
Uint32 format;
int access = 0;
int largeur = 0;
int hauteur = 0;

SDL_QueryTexture(texture,&format, &access, &largeur, &hauteur);

La fonction remplira les variables passées en paramètres avec les informations concernant la texture.

Si vous n'avez pas besoin du format et de l'état d'accès de la texture, vous pouvez passer NULL.

III-D-1. Les paramètres de SDL_QueryTexture()

La fonction accepte quatre paramètres :

  • la texture pour laquelle vous voulez récupérer des informations ;
  • le format des pixels de la texture ;
  • l'état d'accès de la texture ;
  • la largeur ;
  • la hauteur.

Le format, l'état d'accès, la largeur et la hauteur sont des pointeurs afin de permettre à la fonction d'écrire les données.

III-D-2. La valeur de retour de SDL_QueryTexture()

La fonction retourne zéro en cas de réussite ou une valeur négative en cas d'erreur. SDL_GetError() donnera une description de l'erreur.

III-E. Copie du sprite

La copie du sprite en utilisant un SDL_Renderer s'effectue avec SDL_RenderCopy(). De façon similaire à SDL_BlitSurface()Copie du sprite dans la fenêtre, nous avons préparé les éléments suivants :

  • le SDL_Renderer à utiliserCréation d'un SDL_Renderer ;
  • la SDL_Texture à copierCréer la SDL_Texture à partir d'une SDL_Surface.

Voici son utilisation :

Sélectionnez
SDL_Rect dest = { 640/2 - largeur/2,480/2 - hauteur/2, largeur, hauteur};
SDL_RenderCopy(pRenderer,pTexture,NULL,&dest);

Le calcul de la position du sprite peut paraître compliqué. Une explication est donnée dans la partie pour la SDL 1.2Définition de la destination du sprite.

III-E-1. Les paramètres de SDL_RenderCopy()

Cette fonction prend en paramètres :

  • le SDL_Renderer (sachant que le SDL_Renderer est créé à partir d'une fenêtre, il affichera le sprite sur celle-ci) ;
  • la SDL_Texture à copier ;
  • le rectangle source (permettant ainsi de ne copier qu'une sous-partie d'une texture) ;
  • le rectangle destination.

SDL_RenderCopy() prend en compte tous les champs du rectangle destination. En effet, les SDL_Renderer sont capables de faire du redimensionnement de sprite à la volée, ainsi, les informations de largeur et de hauteur sont utiles pour spécifier un quelconque redimensionnement.

III-E-2. La valeur de retour de SDL_RenderCopy()

La fonction retourne zéro lorsque tout a bien été ou un entier négatif en cas d'erreur. SDL_GetError() donnera une description de l'erreur.

III-F. Affichage

Encore une fois, la fenêtre est toujours vide à cette étape. Pour la mettre à jour, il faut utiliser la fonction SDL_RenderPresent() :

Sélectionnez
SDL_RenderPresent(pRenderer);

III-F-1. Le paramètre de SDL_RenderPresent()

La fonction prend en paramètre le SDL_Renderer à mettre à jour.

III-G. Libération de la mémoire liée à la SDL_Texture

Maintenant que la texture n'est plus nécessaire, il est possible de libérer la mémoire associée à celle-ci. La fonction pour ce faire est SDL_DestroyTexture() :

Sélectionnez
SDL_DestroyTexture(pTexture);

III-G-1. Le paramètre de SDL_DestroyTexture()

La fonction prend en paramètre la SDL_Texture à détruire.

III-H. Libération de la mémoire liée au SDL_Renderer

Le SDL_Renderer doit lui aussi être désalloué. La fonction pour ce faire est SDL_DestroyRenderer() :

Sélectionnez
SDL_DestroyRenderer(pRenderer);

III-H-1. Le paramètre de SDL_DestroyRenderer()

La fonction prend en paramètre le SDL_Renderer à détruire.

III-I. Récapitulatif

Voici le code permettant d'afficher un sprite et cela, en utilisant l'accélération graphique disponible dans la SDL 2 :

Sélectionnez
SDL_Renderer *pRenderer = SDL_CreateRenderer(pWindow,-1,SDL_RENDERER_ACCELERATED); // Création d'un SDL_Renderer utilisant l'accélération matérielle
if ( pRenderer )
{
    SDL_Surface* pSprite = SDL_LoadBMP("./data/dvp.bmp");
    if ( pSprite )
    {
        SDL_Texture* pTexture = SDL_CreateTextureFromSurface(pRenderer,pSprite); // Préparation du sprite
        if ( pTexture )
        {
            SDL_Rect dest = { 640/2 - pSprite->w/2,480/2 - pSprite->h/2, pSprite->w, pSprite->h};
            SDL_RenderCopy(pRenderer,pTexture,NULL,&dest); // Copie du sprite grâce au SDL_Renderer

            SDL_RenderPresent(pRenderer); // Affichage
            SDL_Delay(3000); /* Attendre trois secondes, que l'utilisateur voit la fenêtre */

            SDL_DestroyTexture(pTexture); // Libération de la mémoire associée à la texture
        }
        else
        {
            fprintf(stdout,"Échec de création de la texture (%s)\n",SDL_GetError());
        }

        SDL_FreeSurface(pSprite); // Libération de la ressource occupée par le sprite
    }
    else
    {
        fprintf(stdout,"Échec de chargement du sprite (%s)\n",SDL_GetError());
    }

    SDL_DestroyRenderer(pRenderer); // Libération de la mémoire du SDL_Renderer
}
else
{
    fprintf(stdout,"Échec de création du renderer (%s)\n",SDL_GetError());
}

IV. Nettoyage de l'écran

IV-A. SDL 1.X

Pour donner une couleur de fond à votre fenêtre, vous pouvez utiliser SDL_FillRect :

Sélectionnez
Uint32 color = SDL_MapRGB(pWindowSurface->format,100,32,32);
SDL_FillRect(pWindowSurface,NULL, color);

IV-A-1. Les paramètres de SDL_FillRect()

La fonction accepte trois paramètres :

  • la surface à colorier ;
  • le rectangle dans la surface à colorier ou NULL pour colorier l'intégralité de la surface ;
  • la couleur à utiliser.

IV-A-2. La valeur de retour de SDL_FillRect()

La fonction retourne zéro ou un entier négatif si une erreur a eu lieu. Vous pouvez utiliser SDL_GetError().

IV-B. SDL 2

Avec la nouvelle version de la bibliothèque, le nettoyage de l'écran est un processus en deux étapes :

  • la sélection d'une couleur de nettoyage ;
  • le nettoyage.

IV-B-1. Sélection de la couleur de nettoyage

Pour indiquer la couleur de nettoyage à la bibliothèque, il faut utiliser la fonction SDL_SetRenderDrawColor() :

Sélectionnez
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);

IV-B-1-a. Les paramètres de SDL_SetRenderDrawColor()

La fonction accepte cinq paramètres :

  • le SDL_Renderer sur lequel appliquer le changement ;
  • la valeur pour le canal rouge ;
  • la valeur pour le canal vert ;
  • la valeur pour le canal bleu ;
  • la valeur pour le canal alpha.

IV-B-2. La valeur de retour de SDL_SetRenderDrawColor()

La fonction retourne zéro ou un entier négatif si une erreur a eu lieu. Vous pouvez utiliser SDL_GetError().

IV-B-3. Nettoyage

La fonction en SDL 2 pour effectuer le nettoyage est SDL_RenderClear :

Sélectionnez
SDL_RenderClear(renderer);

IV-B-3-a. Le paramètre de SDL_RenderClear()

La fonction demande simplement le SDL_Renderer à nettoyer.

IV-B-3-b. La valeur de retour de SDL_RenderClear()

La fonction retourne zéro ou un entier négatif si une erreur a eu lieu. Vous pouvez utiliser SDL_GetError().


Tutoriel SDL 2

Les événements avec la SDL 2.0

Table des matières

  • Navigation
  • I. Introduction
  • II. Les événements
    • II-A. Le type SDL_Event
      • II-A-1. Récupérer les événements
        • II-A-1-a. SDL_WaitEvent()
          • II-A-1-a-i. Le paramètre de SDL_WaitEvent()
          • II-A-1-a-ii. La valeur de retour de SDL_WaitEvent()
        • II-A-1-b. SDL_WaitEventTimeout()
          • II-A-1-b-i. Les paramètres de SDL_WaitEventTimeout()
          • II-A-1-b-ii. La valeur de retour de SDL_WaitEventTimeout()
        • II-A-1-c. SDL_PollEvent()
          • II-A-1-c-i. Le paramètre de SDL_PollEvent()
          • II-A-1-c-ii. La valeur de retour de SDL_PollEvent()
        • II-A-1-d. Conseils d'utilisation
        • II-A-1-e. Remplissage de la queue d'événements
      • II-A-2. Analyse des événements
        • II-A-2-a. Événement de la fenêtre
        • II-A-2-b. Événement du clavier
          • II-A-2-b-i. SDL_Keysym
        • II-A-2-c. Événements de la souris
          • II-A-2-c-i. Déplacement de la souris
          • II-A-2-c-ii. Appui sur les boutons de la souris
          • II-A-2-c-iii. Utilisation de la roulette
        • II-A-2-d. Événement de joystick
          • II-A-2-d-i. Connexion/Déconnexion de joystick
          • II-A-2-d-ii. Déplacement d'un joystick
          • II-A-2-d-iii. Appui d'un bouton de joystick
          • II-A-2-d-iv. Déplacement de trackball d'un joystick
          • II-A-2-d-v. Déplacement d'un chapeau d'un joystick
    • II-B. Traitement de SDL_Event
    • II-C. Code d'exemple
  • III. Statut des périphériques
    • III-A. Fonctionnement
    • III-B. Les informations de la souris
      • III-B-1. SDL_GetMouseState()
        • III-B-1-a. Les paramètres de SDL_GetMouseState()
        • III-B-1-b. La valeur de retour de SDL_GetMouseState()
          • III-B-1-b-i. La macro SDL_BUTTON
      • III-B-2. SDL_GetRelativeMouseState()
    • III-C. Les informations du clavier
      • III-C-1. SDL_GetKeyboardState()
        • III-C-1-a. Le paramètre de SDL_GetKeyboardState()
          • III-C-1-a-i. La valeur de retour de SDL_GetKeyboardState()
      • III-C-2. SDL_GetModState()
        • III-C-2-a. La valeur de retour de SDL_GetModState()
      • III-C-3. Les informations des joysticks
        • III-C-3-a. SDL_NumJoysticks()
          • III-C-3-a-i. La valeur de retour de SDL_NumJoysticks()
        • III-C-3-b. SDL_JoystickOpen()
          • III-C-3-b-i. Le paramètre de SDL_JoystickOpen()
          • III-C-3-b-ii. La valeur de retour de SDL_JoystickOpen()
        • III-C-3-c. SDL_JoystickName()
          • III-C-3-c-i. Le paramètre de SDL_JoystickName()
          • III-C-3-c-ii. La valeur de retour de SDL_JoystickName()
        • III-C-3-d. SDL_JoystickNumAxes()
          • III-C-3-d-i. Le paramètre de SDL_JoystickNumAxes()
          • III-C-3-d-ii. La valeur de retour de SDL_JoystickNumAxes()
        • III-C-3-e. SDL_JoystickNumButtons()
          • III-C-3-e-i. Le paramètre de SDL_JoystickNumButtons()
          • III-C-3-e-ii. La valeur de retour de SDL_JoystickNumButtons()
        • III-C-3-f. SDL_JoystickNumHats()
          • III-C-3-f-i. Le paramètre de SDL_JoystickNumHats()
          • III-C-3-f-ii. La valeur de retour de SDL_JoystickNumHats()
        • III-C-3-g. SDL_JoystickNumBalls()
          • III-C-3-g-i. Le paramètre de SDL_JoystickNumBalls()
          • III-C-3-g-ii. La valeur de retour de SDL_JoystickNumBalls()
        • III-C-3-h. SDL_JoystickGetAxis()
          • III-C-3-h-i. Les paramètres de SDL_JoystickGetAxis()
          • III-C-3-h-ii. La valeur de retour de SDL_JoystickGetAxis()
        • III-C-3-i. SDL_JoystickGetButton()
          • III-C-3-i-i. Les paramètres de SDL_JoystickGetButton()
          • III-C-3-i-ii. La valeur de retour de SDL_JoystickGetButton()
        • III-C-3-j. SDL_JoystickGetHat()
          • III-C-3-j-i. Les paramètres de SDL_JoystickGetHat()
          • III-C-3-j-ii. La valeur de retour de SDL_JoystickGetHat()
        • III-C-3-k. SDL_JoystickGetBall()
          • III-C-3-k-i. Les paramètres de SDL_JoystickGetBall()
          • III-C-3-k-ii. La valeur de retour de SDL_JoystickGetBall()
        • III-C-3-l. SDL_JoystickUpdate()
        • III-C-3-m. SDL_JoystickEventState()
          • III-C-3-m-i. Le paramètre de SDL_JoystickEventState()
          • III-C-3-m-ii. La valeur de retour de SDL_JoystickEventState()
        • III-C-3-n. SDL_JoystickClose()
          • III-C-3-n-i. Le paramètre de SDL_JoystickClose()
      • III-C-4. Code d'exemple
  • IV. Filtrer des événements
    • IV-A. SDL_SetEventFilter()
      • IV-A-1. Les paramètres de SDL_SetEventFilter()
    • IV-B. SDL_EventState()
    • IV-C. Les paramètres de SDL_EventState()
    • IV-D. La valeur de retour de SDL_EventState()
  • V. Conclusion
  • VI. Remerciements
  • Navigation

Dans le tutoriel précédent, vous avez appris à afficher votre premier sprite. Il est temps d'apprendre à le déplacer !

I. Introduction

Maintenant que vous savez afficher des sprites, il est temps de voir comment le joueur, en appuyant sur son clavier ou sur sa manette, déplace ce sprite.

Dans la SDL 2, les fonctionnalités liées aux événements (que ce soit du clavier ou d'une manette de jeu) n'ont subi que très peu de modification. Vous êtes invités à lire le guide officiel de migration SDL 1.2 vers SDL 2. Ainsi, les connaisseurs de la SDL 1.2 ne seront pas dépaysés.

II. Les événements

Sous le nom d'événement on regroupera toutes les actions qui peuvent influer sur votre programme. On appellera aussi bien l'appui sur une touche du clavier, l'utilisation d'un joystick de la manette, le redimensionnement de la fenêtre, le déplacement de la souris, des événements.

Votre application reçoit de nombreux événements qui sont stockés dans une queue en attente de traitement par votre code. Vous pouvez imaginer cette queue, comme une file d'attente dans un parc d'attractions. Le premier arrivé sera le premier à entrer dans l'attraction. Lorsque vous lisez un événement, c'est donc le premier événement reçu (et donc le plus vieux) qui est lu et retiré.

Vous pouvez lire cette queue avec les fonctions SDL_WaitEvent()SDL_WaitEvent() ou SDL_PollEvent()SDL_PollEvent() ce qui entraînera la suppression de l'événement de la queue (si on reprend l'analogie ci-dessus, la personne qui attendait est donc entrée dans l'attraction, donc elle n'est plus dans la file d'attente). Une fois l'événement retiré de la queue, vous pouvez soit l'analyser et agir suivant son type et les informations associés, soit l'ignorer.

La SDL utilise la structure SDL_Event pour stocker les informations relatives à chaque événement. C'est à travers cette structure que vous pourrez récupérer les informations de chaque événement.

De plus, si vous ne souhaitez pas utiliser la queue d'événements, la SDL permet de connaître l'état de vos périphériques. Ainsi, si vous avez besoin de savoir si tel ou tel bouton est appuyé ou la position de la souris, vous pouvez demander à la bibliothèque de vous retourner les informations voulues. Ce second mécanisme est détaillé dans la section Statut des périphériquesStatut des périphériques.

II-A. Le type SDL_Event

La structure SDL_Event est un conteneur global pour les événements. Dans la SDL, il existe plusieurs types d'événement et chacun peut contenir des informations différentes. En effet, si la SDL doit décrire un clic de souris, alors l'événement doit contenir des informations décrivant ce clic (quel bouton, quelle souris…), mais si la bibliothèque doit décrire un appui sur une touche, alors l'événement doit contenir des informations sur cette touche (quel touche est appuyée…). Que ce soit pour les événements de la souris, ou pour tout autre événement, la SDL utilise la structure SDL_Event. Pour ce faire, la structure possède un champ par type d'événement et un champ indiquant le type d'événement reçu. Ainsi, lorsque vous avez un SDL_Event, vous devez lire le champ type pour savoir quel champ de la structure contient les informations appropriées.

Chaque champ de SDL_Event décrivant un événement est lui-même une structure. En effet, pour décrire complètement un événement, il est nécessaire d'avoir plusieurs données et donc, d'utiliser une structure pour les conserver.

Les types disponibles sont les suivants :

Valeur de type | Structure utilisée pour l'événement | Champ | Description
SDL_CLIPBOARDUPDATE | Aucune structure utilisée pour cet événement. |  | Mise à jour du presse-papier.
SDL_CONTROLLERAXISMOTION | SDL_ControllerAxisEvent | caxis | Données de déplacement d'un axe d'un contrôleur de jeu.
SDL_CONTROLLERBUTTONDOWN SDL_CONTROLLERBUTTONUP | SDL_ControllerButtonEvent | cbutton | Données d'appui ou de relâchement d'un bouton d'un contrôleur de jeu.
SDL_CONTROLLERDEVICEADDED SDL_CONTROLLERDEVICEREMOVED SDL_CONTROLLERDEVICEREMAPPED | SDL_ControllerDeviceEvent | cdevice | Données de connexion/déconnexion/changement de configuration d'un contrôleur de jeu.
SDL_DOLLARGESTURE | SDL_DollarGestureEve nt | dgesture | Données de mouvement sur support tactile.
SDL_DROPFILE | SDL_DropEvent | drop | Données du déposé d'un fichier dans l'application.
SDL_FINGERMOTION SDL_FINGERDOWN SDL_FINGERUP | SDL_TouchFingerEvent | tfinger | Données de déplacement, appui et relâchement sur le périphérique tactile.
SDL_KEYDOWN SDL_KEYUP | SDL_KeyboardEvent | key | Données d'appui ou relâchement d'une touche du clavier.
SDL_JOYAXISMOTION | SDL_JoyAxisEvent | jaxis | Données d'axe d'une manette.
SDL_JOYBALLMOTION | SDL_JoyBallEvent | jball | Données de trackball d'une manette.
SDL_JOYHATMOTION | SDL_JoyHatEvent | jhat | Données du chapeau d'une manette.
SDL_JOYBUTTONDOWN SDL_JOYBUTTONUP | SDL_JoyButtonEvent | jbutton | Données d'appui ou relâchement d'une manette.
SDL_MOUSEMOTION | SDL_MouseMotionEvent | motion | Données de déplacement de la souris.
SDL_MOUSEBUTTONDOWN SDL_MOUSEBUTTONUP | SDL_MouseButtonEvent | button | Données d'appui ou relâchement d'un bouton de la souris.
SDL_MOUSEWHEEL | SDL_MouseWheelEvent | wheel | Données de la roulette de la souris.
SDL_MULTIGESTURE | SDL_MultiGestureEvent | mgesture | Données de touché multiples.
SDL_QUIT | SDL_QuitEvent | quit | Données de demande de fermeture de l'application.
SDL_SYSWMEVENT | SDL_SysWMEvent | syswm | Données dépendantes du gestionnaire de fenêtres.
SDL_TEXTEDITING | SDL_TextEditingEvent | edit | Données de l'édition de texte.
SDL_TEXTINPUT | SDL_TextInputEvent | text | Données de l'entrée d'un texte.
SDL_USEREVENT | SDL_UserEvent | user | Données fournies par l'utilisateur.
SDL_WINDOWEVENT | SDL_WindowEvent | window | Données de l'événement de la fenêtre.

De plus, il existe six événements spécifiques aux appareils mobiles :

Valeur de type | Description
SDL_APP_TERMINATING | Le système termine l'application.
SDL_APP_LOWMEMORY | Le système manque de mémoire ; libérez-en.
SDL_APP_WILLENTERBACKGROUND | L'application est envoyée en tâche de fond.
SDL_APP_DIDENTERBACKGROUND | L'application est en tâche de fond.
SDL_APP_WILLENTERFOREGROUND | L'application est envoyée en premier plan.
SDL_APP_DIDENTERFOREGROUND | L'application est en premier plan.

Ces événements doivent être traités en priorité sinon le système tue votre application. Pour cela, utilisez un filtre d'événementFiltrer des événements.

II-A-1. Récupérer les événements

Pour récupérer un SDL_Event de la queue, trois fonctions sont proposées :

  • SDL_WaitEvent() ;
  • SDL_WaitEventTimeout() ;
  • SDL_PollEvent().

Chacune de ces fonctions récupèrent le plus vieux éléments présents dans la queue d'événements.

II-A-1-a. SDL_WaitEvent()

SDL_WaitEvent() est une fonction bloquante. Cela signifie que lorsque vous l'utilisez, votre programme (du moins, votre thread courant) est bloqué jusqu'à ce que la fonction finisse sa tâche. Cela veut dire que s'il n'y a aucun événement disponible, la fonction attend et ne vous redonnera la main qu'après l'arrivée d'un événement dans la queue.

Son utilisation est simple. Elle prend un pointeur sur un SDL_Event afin de le remplir avec l'événement récupéré :

Sélectionnez
SDL_Event ev;
SDL_WaitEvent(&ev);
// Ici, vous devez analyser le contenu de ev
II-A-1-a-i. Le paramètre de SDL_WaitEvent()

Un pointeur sur SDL_Event. La variable SDL_Event pointée sera remplie avec l'événement récupéré de la queue.

II-A-1-a-ii. La valeur de retour de SDL_WaitEvent()▲

La fonction retourne zéro si une erreur a eu lieu. Vous pouvez utiliser SDL_GetError() pour plus d'informations sur l'erreur.

II-A-1-b. SDL_WaitEventTimeout()

SDL_WaitEventTimeout() est similaire à SDL_WaitEvent() et bloquera aussi l'exécution de votre programme. Toutefois, avec cette fonction vous pouvez spécifier un temps au bout duquel la fonction vous rendra la main, même si aucun événement n'est arrivé.

II-A-1-b-i. Les paramètres de SDL_WaitEventTimeout()

Le premier paramètre est un pointeur sur SDL_Event. La variable SDL_Event pointée sera remplie avec l'événement récupéré de la queue.

Le second paramètre est un entier représentant un temps en milliseconde au bout duquel la fonction rendra la main au programme et cela même s'il n'y a pas d'événement.

II-A-1-b-ii. La valeur de retour de SDL_WaitEventTimeout()

La fonction retourne zéro si une erreur a eu lieu ou si le temps passé en paramètre s'est écoulé. Vous pouvez utiliser SDL_GetError() pour plus d'informations sur l'erreur. La fonction retourne 1, lorsqu'un événement a été récupéré.

II-A-1-c. SDL_PollEvent()

La fonction SDL_PollEvent() effectue la même chose que les fonctions SDL_WaitEvent*(). Contrairement aux autres fonctions SDL_WaitEvent*(), SDL_PollEvent() n'est pas bloquante et ne bloquera donc pas votre programme même lorsqu'il n'y a aucun événement à récupérer.

Sélectionnez
SDL_Event ev;
if ( SDL_PollEvent(&ev) == 1 )
{
    // Analyse de l'événement
}
II-A-1-c-i. Le paramètre de SDL_PollEvent()

Un pointeur sur SDL_Event. La variable SDL_Event pointée sera remplie avec l'événement récupéré de la queue.

II-A-1-c-ii. La valeur de retour de SDL_PollEvent()

La fonction retourne 1, si un événement a été retourné, zéro s'il n'y en a pas.

II-A-1-d. Conseils d'utilisation

En théorie, les fonctions SDL_WaitEvent*() ne sont pas très intéressantes car l'exécution du programme sera bloquée (ou, si ce n'est pas du programme, c'est de la boucle principale). La solution dans laquelle vous utiliseriez un thread n'est pas valable car cette fonction doit être appelée dans le thread initialisant le mode vidéo. Pour des raisons de facilité, il est donc préférable d'utiliser SDL_PollEvent().

Imaginons la boucle principale de votre programme comme étant la suivante :

Sélectionnez
while (1) // Tant que l'on veut que le jeu fonctionne
{
    // Ici, nous devrions traiter les événements
    afficheMonde();
    deplacementMonde(); // On gère ici, tout ce qui est de la mise à jour du monde
}

Si nous utilisions SDL_WaitEvent(), il faudrait attendre un événement de l'utilisateur pour pouvoir afficher notre monde (ou la prochaine image de notre monde).

Il serait possible d'utiliser SDL_WaitEventTimeout() et d'indiquer un temps court, afin d'afficher et de mettre à jour le monde même s'il n'y a pas d'événement. Si vous essayez et que l'utilisateur effectue énormément d'événements, vous n'allez traiter qu'un seul événement par tour de boucle.

Il faut donc insérer SDL_WaitEventTimeout(), ou SDL_PollEvent(), dans une boucle afin de traiter tous les événements présents dans la queue. Ainsi, nous sommes certains de gérer tout ce que l'utilisateur a voulu faire avant d'afficher et de mettre à jour le monde.

La solution avec SDL_WaitEventTimeout(), n'est pas parfaite une fois dans une boucle.

En effet, si nous plaçons SDL_WaitEventTimeout() dans une boucle comme suit :

Sélectionnez
while ( SDL_WaitEventTimeout(&ev,16) ) // Nous traitons les événements de la queue
{
    // Traitement de l'événement
}

il serait possible de complètement bloquer (freeze) le programme. Cela se produirait si le programme recevait un événement toutes les N millisecondes, où N est inférieur au temps passé à la fonction SDL_WaitEventTimeout(). À chaque fois qu'un événement est reçu, le temps est remis à zéro et le programme doit attendre 16 millisecondes (pour ce cas) avant de retourner 0 (et de provoquer la sortie de la boucle).

Si vous pensez que cela arrive rarement, testez par vous-même avec cette boucle et pendant l'exécution du programme bougez rapidement et en continu la souris.

Donc, nous utilisons SDL_PollEvent(). Voici le code avec SDL_PollEvent() :

Sélectionnez
while (1) // Tant que l'on veut que le jeu fonctionne
{
    SDL_Event ev;
    while ( SDL_PollEvent(&ev) ) // Nous traitons les événements de la queue
    {
        // Traitement de 'ev' (nous allons voir après comment traiter un SDL_Event)
    }

    afficheMonde();
    deplacementMonde(); // On gère ici, tout ce qui est de la mise à jour du monde
}

Pour des raisons de simplicité du code et de fluidité d'un jeu vidéo les fonctions SDL_WaitEvent() ou SDL_WaitEventTimeout() sont déconseillées.

II-A-1-e. Remplissage de la queue d'événements

Lors des appels à SDL_WaitEvent(), SDL_WaitEventTimeout() et SDL_PollEvent(), la fonction SDL_PumpEvents() est appelée. Cette fonction permet d'analyser les différents périphériques (clavier, souris…) afin de récupérer tous les nouveaux événements et les mettre dans la queue d'événements. Comme la fonction est implicitement appelée, vous n'avez pas besoin de le faire vous-même.

SDL_PumpEvents() ne peut être appelée que dans le thread initiateur du mode vidéo et la documentation conseille même de ne l'appeler que dans le thread principal. Les fonctions SDL_WaitEvent(), SDL_WaitEventTimeout() et SDL_PollEvent() sont contraintes par cette même règle, car elles appellent SDL_PumpEvents(). Cette limitation est dûe aux détails de l'implémentation de la fonction SDL_PumpEvents() (certains systèmes ne peuvent récupérer les événements autrement).

La fonction ne prend et ne retourne aucune valeur :

Sélectionnez
SDL_PumpEvents();

II-A-2. Analyse des événements

II-A-2-a. Événement de la fenêtre

Lorsque vous recevez un événement de la fenêtre, dont le type est SDL_WINDOWEVENT, vous avez accès aux informations suivantes dans le champ window de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | windowID | La fenêtre sur laquelle l'événement a eu lieu.
Uint8 | event | Un identifiant de l'événement parmi SDL_WindowEventID.
Sint32 | data1 | Données dépendantes à l'événement.
Sint32 | data2

II-A-2-b. Événement du clavier

Lorsque vous recevez un événement du clavier, dont le type est SDL_KEYDOWN ou SDL_KEYUP, vous avez accès aux informations suivantes dans le champ key de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | windowID | La fenêtre sur laquelle l'événement a eu lieu.
Uint8 | state | L'état de la touche (soit SDL_PRESSED lorsqu'enfoncée, soit SDL_RELEASED lorsque relâchée).
Uint8 | repeat | Différent de zéro, si c'est une répétition de touche.
SDL_Keysym | keysym | Une structure SDL_Keysym identifiant la touche.
II-A-2-b-i. SDL_Keysym

La structure SDL_Keysym est utilisée pour identifier une touche. En effet, il existe deux moyens d'identifier la touche initiatrice de l'événement (et cela est quelque peu différent de la SDL 1.2). Voici le contenu de la structure :

Type | Nom | Description
SDL_Scancode | scancode | Code de la touche physique.
SDL_Keycode | sym | Code de la représentation virtuelle de la touche.
Uint16 | mod | Une combinaison des touches spéciales du clavier (Ctrl/Shift/…). Voir SDL_Keymod.
Uint32 |  | Non utilisé

La différence entre la représentation physique de la touche et la représentation virtuelle est importante. En effet, si vous développez un jeu et que vous souhaitez utiliser ZQSD sur votre clavier azerty, un joueur américain aura du mal, car les touches ne sont pas à la même place. Ceci est dû à la représentation virtuelle, qui elle, dépend de la langue. Plus précisément, un clavier américain n'est physiquement pas (ou très peu) différent d'un clavier français, il y a bien six lignes de touches. D'ailleurs, la tabulation est toujours à la même place. Par contre, la configuration du système dit : « pour la touche à droite de la tabulation, je vais afficher 'a' » (si le système est français). Mais si le système est configuré autrement, il peut dire : « pour la touche à droite de la tabulation, je vais afficher 'q' ou encore ''' (clavier dvorak) ». La représentation physique ne changera donc pas, même si la configuration du système change.

II-A-2-c. Événements de la souris

II-A-2-c-i. Déplacement de la souris

Lorsque vous recevez un événement de déplacement de la souris, dont le type est SDL_MOUSEMOTION, vous avez accès aux informations suivantes dans le champ motion de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | windowID | La fenêtre sur laquelle l'événement a eu lieu.
Uint32 | which | Identifiant de la souris (pour les événements venant d'une surface tactile).
Uint32 | state | Combinaison suivant les boutons enfoncés de la souris.
Sint32 | x | Coordonnée en X relative à la fenêtre.
Sint32 | y | Coordonnée en Y relative à la fenêtre.
Sint32 | xrel | Déplacement sur l'axe des X.
Sint32 | yrel | Déplacement sur l'axe des Y.
II-A-2-c-ii. Appui sur les boutons de la souris

Lorsque vous recevez un événement d'appui d'un bouton de la souris, dont le type est SDL_MOUSEBUTTONDOWN ou SDL_MOUSEBUTTONUP, vous avez accès aux informations suivantes dans le champ button de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | windowID | La fenêtre sur laquelle l'événement a eu lieu.
Uint32 | which | Identifiant de la souris (pour les événements venant d'une surface tactile).
Uint8 | button | Le bouton donc le changement d'état a initié l'événement.
Uint8 | state | SDL_PRESSED si le bouton est enfoncé ou SDL_RELEASED si le bouton est relâché.
Uint8 | clicks | 1 pour un clic simple, 2 pour un double clic, etc. (>= SDL 2.0.2).
Sint32 | x | Coordonnée en X relative à la fenêtre.
Sint32 | y | Coordonnée en Y relative à la fenêtre.
II-A-2-c-iii. Utilisation de la roulette

Lorsque vous recevez un événement d'utilisation de la roulette de la souris, dont le type est SDL_MOUSEWHEEL, vous avez accès aux informations suivantes dans le champ wheel de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | windowID | La fenêtre sur laquelle l'événement a eu lieu.
Uint32 | which | Identifiant de la souris (pour les événements venant d'une surface tactile).
Sint32 | x | Valeur de défilement horizontal, positive lorsque le défilement est vers la droite, sinon négative.
Sint32 | y | Valeur de défilement vertical, positive lorsque le défilement est vers le haut, sinon négative.

II-A-2-d. Événement de joystick

II-A-2-d-i. Connexion/Déconnexion de joystick

Lorsque vous recevez un événement de connexion ou de déconnexion d'un joystick, dont le type est respectivement SDL_JOYDEVICEADDDED ou SDL_JOYDEVICEREMOVED, vous avez accès aux informations suivantes dans le champ jdevice de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
Uint32 | which | Lors d'une connexion : l'indice du périphérique. Lors d'une déconnexion : l'identifiant de l'instance.
II-A-2-d-ii. Déplacement d'un joystick

Lorsque vous recevez un événement de déplacement d'un joystick, dont le type est SDL_JOYAXISMOTION, vous avez accès aux informations suivantes dans le champ jaxis de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
SDL_JoystickID | which | L'indice du joystick.
Uint8 | axis | L'indice de l'axe qui a bougé.
Sint16 | value | La position actuelle de l'axe (entre -32768 et 32767).
II-A-2-d-iii. Appui d'un bouton de joystick

Lorsque vous recevez un événement d'appui sur un bouton d'un joystick, dont le type est SDL_JOYBUTTONDOWN ou SDL_JOYBUTTONUP, vous avez accès aux informations suivantes dans le champ jbutton de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
SDL_JoystickID | which | L'indice du joystick.
Uint8 | button | L'indice du bouton initiant l'événement.
Uint8 | state | L'état du bouton (SDL_PRESSED lorsqu'enfoncé et SDL_RELEASED lorsque relâché).
II-A-2-d-iv. Déplacement de trackball d'un joystick

Lorsque vous recevez un événement de déplacement de trackball d'un joystick, dont le type est SDL_JOYBALLMOTION, vous avez accès aux informations suivantes dans le champ jball de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
SDL_JoystickID | which | L'indice du joystick.
Uint8 | ball | L'indice du trackball initiant l'événement.
Sint16 | xrel | Le déplacement sur l'axe des X.
Sint16 | yrel | Le déplacement sur l'axe des Y.
II-A-2-d-v. Déplacement d'un chapeau d'un joystick

Lorsque vous recevez un événement de déplacement d'un chapeau d'un joystick, dont le type est SDL_JOYHATMOTION, vous avez accès aux informations suivantes dans le champ jhat de SDL_Event :

Type | Nom | Description
Uint32 | timestamp | Marqueur temporel.
SDL_JoystickID | which | L'indice du joystick.
Uint8 | hat | L'indice du chapeau initiant l'événement.
Uint8 | value | La nouvelle position du chapeau.

II-B. Traitement de SDL_Event

Maintenant que nous savons récupérer les SDL_Event de la queue des événements, il faut analyser les valeurs reçues.

Le procédé pour ce faire est simple. Vous recevez un SDL_Event de la queue des événements, celui-ci va contenir les informations pour un événement d'un type défini. Il suffit donc de :

  • regarder quel type d'événement vous avez ;
  • lire les informations associées à ce type.

Ainsi, il suffit de faire un switch du type et dans chacun des cas, le code de traitement des informations pour cet événement :

Sélectionnez
SDL_Event event;
if ( SDL_PollEvent(&event) )
{
    switch(event.type)
    {
        case SDL_WINDOWEVENT: // Événement de la fenêtre
            if ( event.window.event == SDL_WINDOWEVENT_CLOSE ) // Fermeture de la fenêtre
            {
                // Action à faire lorsque l'utilisateur clique sur la croix
            }
            break;
        case SDL_KEYUP: // Événement de relâchement d'une touche clavier
            if ( event.key.keysym.sym == SDLK_ESCAPE ) // C'est la touche Échap
            {
                // Action à faire lorsque l'utilisateur relâche la touche Échap
            }
            break;
    }
}

II-C. Code d'exemple

Le mieux pour présenter l'utilisation de tous ces événements est de créer un programme qui se contentera d'afficher un message suivant la touche appuyée.

Tous les événements ne sont pas traités, mais il suffit de regarder la documentation et de suivre le même procédé afin de gérer les événements manquants.

Voici le code source de ce programme :

Sélectionnez
#include <SDL2/SDL.h>

#include <stdio.h>

int main(int argc, char** argv)
{
    /* Initialisation simple */
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK) != 0 )
    {
        fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError());
        return -1;
    }

    {
        /* Création de la fenêtre */
        SDL_Window* pWindow = NULL;
        pWindow = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED,
                                                                  SDL_WINDOWPOS_UNDEFINED,
                                                                  640,
                                                                  480,
                                                                  SDL_WINDOW_SHOWN);

        if (pWindow)
        {
            char cont = 1; /* Détermine si on continue la boucle principale */
            SDL_Event event;
            SDL_Joystick* pJoy=NULL;

            SDL_JoystickEventState(SDL_ENABLE); // On indique que l'on souhaite utiliser le système d'événements pour les joysticks

            while ( cont != 0 )
            {
                while ( SDL_PollEvent(&event) )
                {
                    /* Traitement de l'événement */
                    switch (event.type) /* Quel événement avons-nous ? */
                    {
                        case SDL_KEYDOWN:
                            fprintf(stdout, "Un appuie sur une touche :\n");
                            fprintf(stdout, "\trépétition ? : %d\n",event.key.repeat);
                            fprintf(stdout, "\tscancode : %d\n",event.key.keysym.scancode);
                            fprintf(stdout, "\tkey : %d\n",event.key.keysym.sym);
                            if ( event.key.keysym.scancode == SDL_SCANCODE_ESCAPE )
                            {
                                cont = 0;
                            }
                            break;
                        case SDL_KEYUP:
                            fprintf(stdout, "Un relachement d'une touche :\n");
                            fprintf(stdout, "\trépétition ? : %d\n",event.key.repeat);
                            fprintf(stdout, "\tscancode : %d\n",event.key.keysym.scancode);
                            fprintf(stdout, "\tkey : %d\n",event.key.keysym.sym);
                            break;

                        case SDL_MOUSEMOTION:
                            fprintf(stdout, "Un déplacement de la souris :\n");
                            fprintf(stdout, "\tfenêtre : %d\n",event.motion.windowID);
                            fprintf(stdout, "\tsouris : %d\n",event.motion.which);
                            fprintf(stdout, "\tposition : %d;%d\n",event.motion.x,event.motion.y);
                            fprintf(stdout, "\tdelta : %d;%d\n",event.motion.xrel,event.motion.yrel);
                            break;
                        case SDL_MOUSEBUTTONUP:
                            fprintf(stdout, "Un relachement d'un bouton de la souris :\n");
                            fprintf(stdout, "\tfenêtre : %d\n",event.button.windowID);
                            fprintf(stdout, "\tsouris : %d\n",event.button.which);
                            fprintf(stdout, "\tbouton : %d\n",event.button.button);
    #if SDL_VERSION_ATLEAST(2,0,2)
                            fprintf(stdout, "\tclics : %d\n",event.button.clicks);
    #endif
                            fprintf(stdout, "\tposition : %d;%d\n",event.button.x,event.button.y);
                            break;
                        case SDL_MOUSEBUTTONDOWN:
                            fprintf(stdout, "Un appuie sur un bouton de la souris :\n");
                            fprintf(stdout, "\tfenêtre : %d\n",event.button.windowID);
                            fprintf(stdout, "\tsouris : %d\n",event.button.which);
                            fprintf(stdout, "\tbouton : %d\n",event.button.button);
    #if SDL_VERSION_ATLEAST(2,0,2)
                            fprintf(stdout, "\tclics : %d\n",event.button.clicks);
    #endif
                            fprintf(stdout, "\tposition : %d;%d\n",event.button.x,event.button.y);
                            break;
                        case SDL_MOUSEWHEEL:
                            fprintf(stdout, "Roulette de la souris :\n");
                            fprintf(stdout, "\tfenêtre : %d\n",event.wheel.windowID);
                            fprintf(stdout, "\tsouris : %d\n",event.wheel.which);
                            fprintf(stdout, "\tposition : %d;%d\n",event.wheel.x,event.wheel.y);
                            break;

                        case SDL_JOYDEVICEADDED:
                            fprintf(stdout, "Connexion de joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jdevice.which);

                            if ( pJoy == NULL )
                            {
                                pJoy = SDL_JoystickOpen(event.jdevice.which);
                            }
                            else
                            {
                                fprintf(stdout,"Ce nouveau joystick ne sera pas ouvert (le programme ne gère qu'un joystick)\n");
                            }
                            break;
                        case SDL_JOYDEVICEREMOVED:
                            fprintf(stdout, "Déconnexion de joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jdevice.which);

                            if ( pJoy != NULL )
                            {
                                SDL_JoystickClose(pJoy);
                                pJoy = NULL;
                            }
                            break;
                        case SDL_JOYAXISMOTION:
                            fprintf(stdout, "Déplacement joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jaxis.which);
                            fprintf(stdout, "\taxe : %d\n",event.jaxis.axis);
                            fprintf(stdout, "\tvaleur : %d\n",event.jaxis.value);
                            break;
                        case SDL_JOYBUTTONDOWN:
                            fprintf(stdout, "Appui bouton joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jbutton.which);
                            fprintf(stdout, "\tbutton : %d\n",event.jbutton.button);
                            fprintf(stdout, "\tétat : %d\n",event.jbutton.state);
                            break;
                        case SDL_JOYBUTTONUP:
                            fprintf(stdout, "Relâchement bouton joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jbutton.which);
                            fprintf(stdout, "\tbutton : %d\n",event.jbutton.button);
                            fprintf(stdout, "\tétat : %d\n",event.jbutton.state);
                            break;
                        case SDL_JOYBALLMOTION:
                            fprintf(stdout, "Déplacement de trackball :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jball.which);
                            fprintf(stdout, "\ttrackball : %d\n",event.jball.ball);
                            fprintf(stdout, "\tdéplacement : %d;%d\n",event.jball.xrel,event.jball.yrel);
                            break;
                        case SDL_JOYHATMOTION:
                            fprintf(stdout, "Déplacement de chapeau d'un joystick :\n");
                            fprintf(stdout, "\tjoystick : %d\n",event.jhat.which);
                            fprintf(stdout, "\tbutton : %d\n",event.jhat.hat);
                            fprintf(stdout, "\tvaleur : %d\n",event.jhat.value);
                            break;

                        case SDL_WINDOWEVENT:
                            fprintf(stdout, "Un événement de fenêtre, sur la fenêtre : %d\n",event.window.windowID);
                            // En théorie, ici, il faudrait faire un autre test ou switch pour chaque type de cet événement
                            break;
                        default:
                            fprintf(stdout, "Événement non traité : %d\n",event.type);
                    }

                    fprintf(stdout, "\n");
                }

                /* On a traité les événements, on peut continuer le jeu */
            }

            if ( pJoy != NULL )
            {
                SDL_JoystickClose(pJoy);
                pJoy = NULL;
            }

            SDL_DestroyWindow(pWindow);
        }
        else
        {
            fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError());
        }
    }

    SDL_Quit();

    return 0;
}

III. Statut des périphériques

La méthode précédente pour récupérer les événements ne correspond pas à tous les besoins. Dans certaines applications, ou jeux, il est préférable de vérifier si une touche est appuyée et ce, à n'importe quel moment.

Avec la méthode précédente, vous devez créer un tableau, pour conserver l'état appuyé ou non de vos touches. Sachant que la SDL propose un mécanisme similaire, il est préférable de l'utiliser au lieu de le reprogrammer soi-même.

En effet, il est possible de connaître l'état des périphériques et donc de savoir si des touches du clavier sont appuyées, ou encore, l'état de la manette (boutons, joysticks…) et cela, sans avoir besoin d'analyser la queue d'événements.

III-A. Fonctionnement

La mise en place est encore plus simple que pour les SDL_Event. Ici, il suffit d'appeler SDL_PumpEvents() avant de récupérer l'état des périphériques pour que la SDL mette à jour ses informations sur tous les périphériques.

Voici un exemple pour la position de la souris :

Sélectionnez
int x, y;
Uint32 boutons;

SDL_PumpEvents();
boutons = SDL_GetMouseState(&x,&y);
// Vous n'avez plus qu'à utiliser x, y et/ou boutons

SDL_PumpEvents() ne peut être appelée que dans le thread initiateur du mode vidéo et la documentation conseille même de ne l'appeler que dans le thread principal.

III-B. Les informations de la souris

III-B-1. SDL_GetMouseState()

SDL_GetMouseState() permet de récupérer la position de la souris en coordonnées de fenêtre (0, 0 correspond au coin supérieur gauche et le coin inférieur droit aura les valeurs de la taille de la fenêtre). De plus, la valeur renvoyée dépend des boutons appuyés. Pour déterminer quels sont les boutons appuyés, vous devez utiliser la macro SDL_BUTTON(X) (où X correspond généralement à 1, pour le bouton de gauche, 2 pour le bouton du milieu et 3 pour le bouton de droite), par exemple :

Sélectionnez
SDL_PumpEvents();
if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(1))
  printf("Le bouton 1(gauche) est appuyé.\n");

III-B-1-a. Les paramètres de SDL_GetMouseState()

La fonction prend deux pointeurs en paramètre. Les valeurs pointées vont être remplies avec les valeurs X et Y de la position de la souris. Vous pouvez aussi passer NULL et dans ce cas, vous n'aurez pas les coordonnées de la souris.

III-B-1-b. La valeur de retour de SDL_GetMouseState()

La valeur de retour est un nombre binaire, où chaque bit correspond à un bouton de la souris. Pour vérifier si un bouton est appuyé, vous devez utiliser un masque afin de filtrer le bit correspondant au bouton voulu. Pour créer ce masque vous pouvez utiliser la macro SDL_BUTTON() :

Sélectionnez
SDL_PumpEvents();
if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(1))
  printf("Le bouton 1(gauche) est appuyé.\n");
III-B-1-b-i. La macro SDL_BUTTON

SDL_BUTTON est une macro permettant de créer un entier spécifique pour tester si tel ou tel bouton est appuyé. En effet, la valeur retournée par SDL_GetMousteState() est un nombre binaire ou les bits mis à un représentent les boutons appuyés. La macro va générer un entier avec un bit à 1, pour le bouton correspondant à l'index que vous passez. Il est ensuite donc possible de faire un ET logique, pour déterminer si ce bit est aussi à 1 dans le nombre retourné par la fonction SDL_GetMousteState().

III-B-2. SDL_GetRelativeMouseState()

La fonction SDL_GetRelativeMouseState() est similaire à SDL_GetMouseState(). L'unique différence est que les valeurs de la position de la souris retournées ne sont pas par rapport à la fenêtre, mais par rapport à l'ancienne position de la souris lors du précédent appel à SDL_GetRelativeMouseState(). Avec cette fonction, vous allez donc recevoir le déplacement de la souris effectué entre deux appels à la fonction. Une valeur positive indique un déplacement sur la droite pour l'axe des X et un déplacement vers le bas pour l'axe des Y.

Les paramètres et valeur de retour fonctionnent exactement comme ceux de SDL_GetMouseState().

III-C. Les informations du clavier

III-C-1. SDL_GetKeyboardState()

La fonction SDL_GetKeyboardState() permet de récupérer l'état de toutes les touches du clavier. Pour cela, la fonction retourne un pointeur sur un tableau où chaque case correspond à une touche. La valeur correspond à l'état de la touche (0 si elle n'est pas appuyée). Ainsi, pour tester si une touche est appuyée, vous pouvez écrire le code suivant :

Sélectionnez
const Uint8 *state = SDL_GetKeyboardState(NULL);
if (state[SDL_SCANCODE_RETURN]) {
    printf(« La touche entree est appuyee.
 »);
}

La fonction ne prend pas en compte si la touche majuscule est utilisée ou non.

III-C-1-a. Le paramètre de SDL_GetKeyboardState()

La fonction SDL_GetKeyboardState() accepte un unique paramètre : un pointeur sur un entier. Ce pointeur permet à la fonction de renseigner la taille du tableau. Vous pouvez passer NULL, dans ce cas, vous n'obtiendrez pas la taille du tableau retourné.

III-C-1-a-i. La valeur de retour de SDL_GetKeyboardState()

La fonction SDL_GetKeyboardState() retourne un pointeur sur un tableau correspondant aux états de chacune des touches du clavier. Les indices à utiliser pour accéder à ce tableau sont les SDL_Scancode. Si la valeur correspondant à tel ou tel indice est 1, alors la touche désignée par l'indice est appuyée. Sinon, la valeur sera 0. Ce tableau est géré par la SDL. Vous ne devez donc pas faire de free() dessus.

III-C-2. SDL_GetModState()

La fonction SDL_GetModState() retourne une combinaison de valeurs dépendantes des touches spéciales du clavier (Ctrl/Shift/…). La liste des valeurs pouvant être retournées est accessible ici : SDL_Keymod. La valeur peut être une combinaison (OU logique) de ces valeurs.

III-C-2-a. La valeur de retour de SDL_GetModState()

La valeur de retour de SDL_GetModState() est l'une des suivantes, ou une combinaison (OU logique) de celles-ci.

III-C-3. Les informations des joysticks

Les joysticks, pour être utilisés, doivent être ouverts et fermés (comme un fichier, par exemple). Ainsi, la première étape sera de déterminer le nombre de joysticks disponibles sur la machine avec la fonction SDL_NumJoysticks().

N'oubliez pas d'initialiser le système SDL_INIT_JOYSTICK lors de l'appel à SDL_Init() ou avec un appel à SDL_InitSubSystem().

Une fois que vous êtes certain qu'au moins un joystick est connecté à la machine, vous pouvez « l'ouvrir » avec SDL_JoystickOpen(). Vous devez le faire autant de fois qu'il y a de joysticks si vous souhaitez accéder à tous les joysticks connectés.

La fonction vous retournera un pointeur vers un SDL_Joystick vous permettant d'identifier ce joystick par la suite (nécessaire pour utiliser les autres fonctions liées aux joysticks).

Vous pouvez obtenir des informations sur le nom du joystick, son nombre
d'axes, de boutons, de chapeaux et de trackballs à l'aide de : SDL_JoystickName(),
SDL_JoystickNumAxes(),
SDL_JoystickNumButtons(), SDL_JoystickNumHats(),
SDL_JoystickNumBalls() respectivement.

En ayant pleine connaissance du joystick accessible, vous pouvez analyser
l'état des axes, boutons, chapeaux et trackballs avec les fonctions :
SDL_JoystickGetAxis(),
SDL_JoystickGetButton(), SDL_JoystickGetHat(),
SDL_JoystickGetBall() respectivement.

Notez que vous devez utiliser la fonction SDL_JoystickUpdate() avant d'essayer de récupérer les informations sur les joysticks afin que la SDL mette à jour les informations sur les joysticks.

Finalement, lorsque vous n'avez plus besoin d'utiliser tel ou tel joystick, vous devez utiliser la fonction SDL_JoystickClose().

III-C-3-a. SDL_NumJoysticks()

La fonction SDL_NumJoysticks() retourne le nombre de joysticks présent sur le système.

III-C-3-a-i. La valeur de retour de SDL_NumJoysticks()

SDL_NumJoysticks() retourne le nombre de joysticks présents sur le système ou un nombre négatif en cas d'erreur. Vous pouvez récupérer plus de précision sur l'erreur avec la fonction SDL_GetError().

III-C-3-b. SDL_JoystickOpen()

La fonction SDL_JoystickOpen() permet d'initialiser un joystick en vue d'une utilisation future. La fonction retourne un pointeur sur la structure SDL_Joystick* vous permettant d'identifier le joystick.

III-C-3-b-i. Le paramètre de SDL_JoystickOpen()

SDL_JoystickOpen() n'accepte qu'un paramètre : le numéro du joystick à ouvrir. Ce numéro doit être un nombre entre 0 et le nombre de joysticks connectés (voir SDL_NumJoysticks()).

Ce numéro de joystick n'est pas le même que l'identifiant d'instance utilisé pour identifier le joystick dans les événements.

III-C-3-b-ii. La valeur de retour de SDL_JoystickOpen()

SDL_JoystickOpen() retourne un pointeur sur une instance de SDL_Joystick, ou NULL en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-c. SDL_JoystickName()

La fonction SDL_JoystickName() permet d'obtenir le nom associé à un joystick.

III-C-3-c-i. Le paramètre de SDL_JoystickName()

SDL_JoystickName() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick pour lequel le nom doit être retourné.

III-C-3-c-ii. La valeur de retour de SDL_JoystickName()

SDL_JoystickName() retourne le nom du joystick sous la forme d'une chaîne de caractères ou NULL en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-d. SDL_JoystickNumAxes()

La fonction SDL_JoystickNumAxes() permet de connaître le nombre d'axes présent sur un joystick.

III-C-3-d-i. Le paramètre de SDL_JoystickNumAxes()

SDL_JoystickNumAxes() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick pour lequel le nombre d'axes doit être retourné.

III-C-3-d-ii. La valeur de retour de SDL_JoystickNumAxes()

SDL_JoystickNumAxes() retourne le nombre d'axes disponibles sur le joystick ou une valeur négative en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-e. SDL_JoystickNumButtons()

La fonction SDL_JoystickNumButtons() permet de connaître le nombre de boutons présents sur un joystick.

III-C-3-e-i. Le paramètre de SDL_JoystickNumButtons()

SDL_JoystickNumButtons() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick pour lequel le nombre de boutons doit être retourné.

III-C-3-e-ii. La valeur de retour de SDL_JoystickNumButtons()

SDL_JoystickNumButtons() retourne le nombre de boutons disponibles sur le joystick ou une valeur négative en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-f. SDL_JoystickNumHats()

La fonction SDL_JoystickNumHats() permet de connaître le nombre de chapeaux présents sur un joystick.

III-C-3-f-i. Le paramètre de SDL_JoystickNumHats()

SDL_JoystickNumHats() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick pour lequel le nombre de chapeaux doit être retourné.

III-C-3-f-ii. La valeur de retour de SDL_JoystickNumHats()

SDL_JoystickNumHats() retourne le nombre de chapeaux disponibles sur le joystick ou une valeur négative en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-g. SDL_JoystickNumBalls()

La fonction SDL_JoystickNumBalls() permet de connaître le nombre de trackballs présentes sur un joystick.

III-C-3-g-i. Le paramètre de SDL_JoystickNumBalls()

SDL_JoystickNumBalls() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick pour lequel le nombre de trackballs doit être retourné.

III-C-3-g-ii. La valeur de retour de SDL_JoystickNumBalls()

SDL_JoystickNumBalls() retourne le nombre de trackballs disponibles sur le joystick ou une valeur négative en cas d'erreur. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-h. SDL_JoystickGetAxis()

La fonction SDL_JoystickGetAxis() permet d'obtenir la valeur associée à un axe.

III-C-3-h-i. Les paramètres de SDL_JoystickGetAxis()

SDL_JoystickGetAxis() accepte un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick et un entier compris entre 0 et la valeur retournée par SDL_JoystickNumAxes(), identifiant l'axe pour lequel la valeur est retournée. Généralement, l'axe X est représenté par 0 et l'axe Y par 1.

III-C-3-h-ii. La valeur de retour de SDL_JoystickGetAxis()

La valeur retournée par SDL_JoystickGetAxis() représente la position de l'axe. Elle est comprise entre -32768 et 32767. Il peut être nécessaire d'appliquer quelques tolérances pour éviter les tremblements.

III-C-3-i. SDL_JoystickGetButton()

La fonction SDL_JoystickGetButton() permet d'obtenir la valeur associée à un bouton.

III-C-3-i-i. Les paramètres de SDL_JoystickGetButton()

SDL_JoystickGetButton() accepte un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick et un entier compris entre 0 et la valeur retournée par SDL_JoystickNumButtons(), identifiant le bouton pour lequel la valeur est retournée.

III-C-3-i-ii. La valeur de retour de SDL_JoystickGetButton()

La valeur retournée par SDL_JoystickGetButton() est soit 0 pour un bouton relâché et 1 pour un bouton appuyé.

III-C-3-j. SDL_JoystickGetHat()

La fonction SDL_JoystickGetHat() permet d'obtenir la valeur associée à un axe.

III-C-3-j-i. Les paramètres de SDL_JoystickGetHat()

SDL_JoystickGetHat() accepte un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick et un entier compris entre 0 et la valeur retournée par SDL_JoystickNumHats(), identifiant l'axe pour lequel la valeur est retournée.

III-C-3-j-ii. La valeur de retour de SDL_JoystickGetHat()

La valeur retournée par SDL_JoystickGetHat() représente la position du chapeau. Elle peut être l'une de ces valeurs :

SDL_HAT_CENTERED | Position centrée (ou relâchée)
SDL_HAT_UP | Position haut
SDL_HAT_RIGHT | Position droite
SDL_HAT_DOWN | Position bas
SDL_HAT_LEFT | Position gauche
SDL_HAT_RIGHTUP | Position haut droite
SDL_HAT_RIGHTDOWN | Position bas droite
SDL_HAT_LEFTUP | Position haut gauche
SDL_HAT_LEFTDOWN | Position bas gauche

III-C-3-k. SDL_JoystickGetBall()

La fonction SDL_JoystickGetBall() permet d'obtenir la valeur associée à une trackball.

III-C-3-k-i. Les paramètres de SDL_JoystickGetBall()

SDL_JoystickGetBall() accepte un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick, un entier compris entre 0 et la valeur retournée par SDL_JoystickNumBalls(). Les troisième et quatrième paramètres sont des pointeurs sur des entier permettant à la fonction de retourner des valeurs. Les valeurs retournées à travers ces pointeurs correspondent au déplacement effectué avec la trackball depuis le dernier appel à SDL_JoystickUpdate().

III-C-3-k-ii. La valeur de retour de SDL_JoystickGetBall()

La valeur retournée par SDL_JoystickGetBall() est négative si une erreur a eu lieu, sinon c'est 0. Il est possible d'avoir plus d'informations sur l'erreur avec SDL_GetError().

III-C-3-l. SDL_JoystickUpdate()

La fonction SDL_JoystickUpdate() permet de mettre à jour les données relatives aux joysticks. La fonction est automatiquement appelée dans la boucle événementielle si les événements d'un joystick sont activés.

III-C-3-m. SDL_JoystickEventState()

La fonction SDL_JoystickEventState() permet d'indiquer si les événements des joysticks doivent être pris en compte dans la boucle événementielle ou non. Si les événements joystick sont désactivés, vous devez appeler SDL_JoystickUpdate() vous-même.

La documentation conseille de laisser cette option activée.

III-C-3-m-i. Le paramètre de SDL_JoystickEventState()

Le paramètre de la fonction SDL_JoystickEventState() est une valeur parmi les trois suivantes :

SDL_ENABLE | Active le type d'événement.
SDL_IGNORE | Désactive le type d'événement.
SDL_QUERY | Permet de connaître l'état actuel (activé ou désactivé) d'un type d'événement et n'applique aucun changement.
III-C-3-m-ii. La valeur de retour de SDL_JoystickEventState()

La valeur de retour est l'état précédent le changement d'état. Si le second paramètre de la fonction est SDL_QUERY, la valeur retournée est l'état actuel pour le type d'événement indiqué par le premier paramètre.

III-C-3-n. SDL_JoystickClose()

La fonction SDL_JoystickClose() permet de fermer un joystick précédemment ouvert avec SDL_JoystickOpen().

III-C-3-n-i. Le paramètre de SDL_JoystickClose()

SDL_JoystickClose() accepte uniquement un pointeur sur une instance de SDL_Joystick permettant d'identifier le joystick le plus utilisé.

III-C-4. Code d'exemple

Le mieux pour présenter l'utilisation de toutes ces fonctions est de créer un programme qui se contentera d'afficher un message suivant la touche appuyée. Voici le code source de ce programme :

Sélectionnez
#include <SDL2/SDL.h>
#include <SDL2/SDL_version.h>

#include <stdio.h>

int main(int argc, char** argv)
{
    /* Initialisation simple */
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK) != 0 )
    {
        fprintf(stdout,"Échec de l'initialisation de la SDL (%s)\n",SDL_GetError());
        return -1;
    }

    {
        /* Création de la fenêtre */
        SDL_Window* pWindow = NULL;
        pWindow = SDL_CreateWindow("Ma première application SDL2",SDL_WINDOWPOS_UNDEFINED,
                                                                  SDL_WINDOWPOS_UNDEFINED,
                                                                  640,
                                                                  480,
                                                                  SDL_WINDOW_SHOWN);

        if (pWindow)
        {
        char cont = 1; /* Détermine si on continue la boucle principale */

            while ( cont != 0 )
            {
                SDL_PumpEvents(); // On demande à la SDL de mettre à jour les états sur les périphériques

                // Clavier
                {
                    const Uint8* pKeyStates = SDL_GetKeyboardState(NULL);
                    if ( pKeyStates[SDL_SCANCODE_ESCAPE] )
                    {
                        cont = 0;
                    }
                    // On peut vérifier d'autres touches, si on le souhaite

                    SDL_Keymod mod = SDL_GetModState();
                    if ( mod != KMOD_NONE )
                    {
                        fprintf(stdout,"Vous avez appuyé sur une touche spéciale : %d\n",mod);
                    }
                }
                fprintf(stdout,"\n");
                // Souris
                {
                    int x=0;
                    int y=0;
                    Uint32 boutons = SDL_GetMouseState(&x,&y);

                    fprintf(stdout, "Position de la souris : %d;%d\n",x,y);
                    fprintf(stdout, "Bouton de la souris : %d\n",boutons);

                    SDL_GetRelativeMouseState(&x,&y);
                    fprintf(stdout, "Déplacement de la souris : %d;%d\n",x,y);
                }
                fprintf(stdout,"\n");

                // Joystick
                {
                    int nbJoysticks = SDL_NumJoysticks();
                    int i = 0;
                    for ( i = 0 ; i < nbJoysticks ; i++ )
                    {
                        SDL_Joystick* pJoy = SDL_JoystickOpen(i);

                        SDL_JoystickUpdate();

                        if (pJoy)
                        {
                            printf("Joystick %d\n",i);
                            printf("Nom: %s\n", SDL_JoystickNameForIndex(i));

                            // Axes
                            {
                                int nbAxes = SDL_JoystickNumAxes(pJoy);
                                int axe = 0;

                                printf("Nombre d'axes : %d\n", nbAxes);
                                for ( axe = 0 ; axe < nbAxes ; axe++ )
                                {
                                    printf("Axe %d : %d\n",axe,SDL_JoystickGetAxis(pJoy,axe));
                                }
                            }

                            // Boutons
                            {
                                int nbBoutons = SDL_JoystickNumButtons(pJoy);
                                int bouton = 0;

                                printf("Nombre de boutons : %d\n", nbBoutons);
                                for ( bouton = 0 ; bouton < nbBoutons ; bouton++ )
                                {
                                    printf("Bouton %d : %d\n",bouton,SDL_JoystickGetButton(pJoy,bouton));
                                }
                            }

                            // Trackballs
                            {
                                int nbBalls = SDL_JoystickNumBalls(pJoy);
                                int ball = 0;

                                printf("Nombre de trackballs : %d\n", nbBalls);
                                for ( ball = 0 ; ball < nbBalls ; ball++ )
                                {
                                    int dx=0;
                                    int dy=0;
                                    SDL_JoystickGetBall(pJoy,ball,&dx,&dy);
                                    printf("Déplacement trackball %d : %d;%d\n",ball,dx,dy);
                                }
                            }

                            // Chapeaux
                            {
                                int nbChapeaux = SDL_JoystickNumHats(pJoy);
                                int chapeau = 0;

                                printf("Nombre de chapeaux : %d\n", nbChapeaux);
                                for ( chapeau = 0 ; chapeau < nbChapeaux ; chapeau++ )
                                {
                                    printf("Chapeau %d : %d\n",chapeau,SDL_JoystickGetHat(pJoy,chapeau));
                                }
                            }

                            SDL_JoystickClose(pJoy);
                        }
                        else
                        {
                            printf("Couldn't open Joystick 0\n");
                        }
                    }
                }
                fprintf(stdout,"\n");

                /* On a traité les événements, on peut continuer le jeu */

                /* On ralentit un peu le programme */
                SDL_Delay(10);
            }
            SDL_DestroyWindow(pWindow);
        }
        else
        {
            fprintf(stderr,"Erreur de création de la fenêtre: %s\n",SDL_GetError());
        }
    }

    SDL_Quit();

    return 0;
}

Le programme ne surveille pas tous les événements. Pour les événements manquants, veuillez vous référer à la documentation. Il ne devrait plus y avoir de difficultés à utiliser ce genre de fonctions.

IV. Filtrer des événements

Il est possible (et nécessaire pour les appareils mobiles) d'appliquer des filtres sur les événements. Pour cela, vous allez indiquer à la SDL (avec SDL_SetEventFilter()) une fonction propre à vous, qui sera appelée avant que l'événement reçu par la SDL n'intègre la queue d'événements.

Suivant la valeur de retour de votre fonction, l'événement sera ajouté ou non à la queue d'événements.

Si vous développez une application pour les appareils mobiles, les six événements suivants doivent être traités au plus tôt sans quoi, le système stoppera votre application :

Valeur de type | Description
SDL_APP_TERMINATING | Le système termine l'application.
SDL_APP_LOWMEMORY | Le système manque de mémoire ; libérez-en.
SDL_APP_WILLENTERBACKGROUND | L'application est envoyée en tâche de fond.
SDL_APP_DIDENTERBACKGROUND | L'application est en tâche de fond.
SDL_APP_WILLENTERFOREGROUND | L'application est envoyée en premier plan.
SDL_APP_DIDENTERFOREGROUND | L'application est en premier plan.

Pour être certain qu'ils soient traités au plus tôt, vous pouvez utiliser un filtre d'événements avec SDL_SetEventFilter().

Si vous voulez complètement désactiver certains types d'événements (par exemple les événements de la souris dans un jeu de combat), vous pouvez utiliser la fonction SDL_EventState().

IV-A. SDL_SetEventFilter()

La fonction SDL_SetEventFilter() permet de donner une fonction de votre code qui sera appelée pour chaque événement avant que celui-ci n'intègre la queue d'événements.

La fonction que vous devez indiquer à SDL_SetEventFilter() doit avoir la signature suivante :

Sélectionnez
int nomDeLaFonction(void* userdata, SDL_Event* event);

Votre fonction devra donc prendre deux paramètres : userdata, des données quelconques définies par vous et event, un pointeur sur l'événement à traiter.

De plus, vous devez retourner un entier, soit 1, pour indiquer que l'événement doit être ajouté à la queue des événements, soit 0, pour qu'il ne soit pas ajouté.

Les événements désactivés (avec SDL_EventState()) ne sont pas traités par la fonction de filtre.

Voici un exemple d'utilisation de la fonction, permettant de cacher tous les événements du clavier :

Sélectionnez
int monFiltre(void* userdata, SDL_Event* event)
{
    if (event->type == SDL_KEYDOWN ||
        Event->type == SDL_KEYUP )
    {
        return 0; // ne seront pas ajoutés
    }
    Return 1; // N'oublions pas de laisser passer les autres événements
}

// On met en place le filtre
SDL_SetEventFilter(monFiltre, NULL);

IV-A-1. Les paramètres de SDL_SetEventFilter()

SDL_SetEventFilter() accepte deux paramètres :

  • un SDL_EventFilter(), un pointeur de fonction menant à la fonction que vous souhaitez utilisée pour le filtrage des événements. Le prototype doit être int nomDeLaFonction(void* userdata, SDL_Event* event);
  • un pointeur void* vous permettant de passer n'importe quelles données que vous souhaitez à votre filtre.

IV-B. SDL_EventState()

SDL_EventState() permet de complètement désactiver un type d'événements. Ce faisant, il n'apparaîtra plus dans la queue d'événements. Cela peut être utile lorsque l'utilisateur produit beaucoup d'événements dont vous ne vous souciez pas et qui pourrait causer le ralentissement de votre code de traitement de la queue d'événements.

Voici un exemple d'utilisation de la fonction SDL_EventState() :

Sélectionnez
SDL_EventState(SDL_MOUSEWHEEL,SDL_DISABLE); // Désactive la roulette de la souris

IV-C. Les paramètres de SDL_EventState()

Le premier paramètre de la fonction SDL_EventState() est un type d'événement SDL_EventType indiquant sur quel événement la fonction doit agir. Le second paramètre est une valeur parmi les trois suivantes :

SDL_ENABLE | Active le type d'événement.
SDL_IGNORE ou SDL_DISABLE | Désactive le type d'événement.
SDL_QUERY | Permet de connaître l'état actuel (activé ou désactivé) d'un type d'événement et n'applique aucun changement.

IV-D. La valeur de retour de SDL_EventState()

La valeur de retour est l'état précédent le changement d'état. Si le second paramètre de la fonction est SDL_QUERY, la valeur retournée est l'état actuel pour le type d'événement indiqué par le premier paramètre.

V. Conclusion

Vous avez maintenant appris à traiter les actions que l'utilisateur peut faire pendant qu'il utilise votre programme. Ainsi, grâce aux événements, vous pouvez déplacer le personnage du joueur ou lui permettre de viser avec la souris.

Le choix de la gestion des événements, que ce soit par la queue d'événements ou les états des périphériques est un choix dépendant de votre jeu et de vos besoins. En elles-mêmes, les deux méthodes fonctionnent et vous permettront de récupérer les événements, mais suivant votre jeu, l'une sera plus facile pour réaliser vos désirs. Il est donc nécessaire de bien penser cette partie afin de réaliser un jeu plaisant pour le joueur où les contrôles réagissent bien et ne bloquent pas la liberté de mouvement.

Votre jeu se complète et possède enfin une composante dynamique grâce à laquelle le joueur peut agir sur le monde qu'il voit.

VI. Remerciements

Merci à archMqx. et Kannagi pour leurs précieuses suggestions ainsi que Phanloga pour sa correction orthographique.


Exemple

Exemple 5


Exemple

Exemple 6


Exemple

Exemple 7


Tutoriel SDL

SDL 1.2 to 2.0 Migration Guide wiki.libsdl.org

Contents

  1. SDL 1.2 to 2.0 Migration Guide
    1. Translations
    2. Introduction
      1. Overview of new features
      2. Looking for more information
    3. Moving from SDL 1.2 to 2.0
      1. Some general truths
      2. Video
        1. Setting up a game with the new video API
        2. If your game just wants to get fully-rendered frames to the screen
        3. If your game wants to blit surfaces to the screen
        4. If your game wants to do both
        5. Other Renderer API notes
      3. OpenGL
      4. Input
      5. Events
      6. Audio
      7. Joysticks
      8. Threads
      9. Audio CDs
      10. Dead platforms
      11. Mobile platforms
      12. RWops
      13. Add-on libraries
      14. Summary of some renamed or replaced things
      15. Other stuff

Translations

  • Une traduction de cette page par Developpez.com est disponible ici.

  • If you want to translate this page to other languages, let Ryan know: icculus@icculus.org

Introduction

After many years in development, SDL 2.0 has finally been released!

We are quite proud of it, and we'd like games that are using SDL 1.2 to move up right away. As this can feel like a daunting task, this document is a simple walkthrough of how to migrate to the new library. We think you'll find it's not as hard as you think, and often times, you'll be either replacing function calls with direct equivalents or undoing some hacks in your code to deal with 1.2 deficiencies.

We think you'll be very happy with SDL 2.0, both for the new features and the better experience over SDL 1.2. This document doesn't try to cover all the awesome new things in SDL2--and there are many--but just the things you need to do to get running right now. Once you've ported your code, definitely check out the new stuff, you'll probably want to add some of it to your application, too.

Overview of new features

These are the most important new features in SDL 2.0:

  • Full 3D hardware acceleration
  • Support for OpenGL 3.0+ in various profiles (core, compatibility, debug, robust, etc)
  • Support for OpenGL ES
  • Support for multiple windows
  • Support for multiple displays
  • Support for multiple audio devices
  • Android and iOS support
  • Simple 2D rendering API that can use Direct3D, OpenGL, OpenGL ES, or software rendering behind the scenes
  • Force Feedback available on Windows, Mac OS X and Linux
  • XInput and XAudio2 support for Windows
  • Atomic operations
  • Power management (exposes battery life remaining, etc)
  • Shaped windows
  • 32-bit audio (int and float)
  • Simplified Game Controller API (the Joystick API is still here, too!)
  • Touch support (multitouch, gestures, etc)
  • Better fullscreen support
  • Better keyboard support (scancodes vs keycodes, etc).
  • Message boxes
  • Clipboard support
  • Basic Drag'n'Drop support
  • Proper unicode input and IME support

  • A really powerful assert macro

  • zlib license instead of LGPL.
  • Lots of old annoyances from 1.2 are gone
  • Many other things!

The Introduction page has a more extensive listing about what features SDL offers in overall (including old 1.2 features).

Looking for more information

The best places for information are:

  • this wiki :)

  • the tests included in SDL, in the test/ directory (browse online)

  • the SDL mailing list

Moving from SDL 1.2 to 2.0

Some general truths

There is no compatibility layer built in to SDL2. If an API changed for 2.0, we've changed or removed the old functions where it makes sense. If you point your 1.2 program at the 2.0 headers, it will probably fail to compile. This document will try to talk you through the most important changes, and the ones most likely to trip you up.

There's no SDL_main! Well, okay, there is, and it now does what it was always meant to: be a small piece of code that hides the difference between main() and WinMain() on Windows. There's no initialization code in it, and it's completely optional. This means you can use SDL without it taking over your mainline, which is nice for plugins that use SDL, or scripting languages with an SDL module. All the stuff you'd want the 1.2 SDL_main for is now in SDL_Init() where it belongs.

There's no SDL parachute anymore. What 1.2 called SDL_INIT_NOPARACHUTE is a default and only state now. This would cause problems if something other than the main thread crashed, and it would interfere with apps setting up their own signal/exception handlers. On the downside, some platforms don't clean up fullscreen video well when crashing. You should install your own crash handler, or call SDL_Quit() in an atexit() function or whatnot if this is a concern. Note that on Unix platforms, SDL still catches SIGINT and maps it to an SDL_QUIT event.

Video

Setting up a game with the new video API

The video API is the most dramatic change from 1.2. Needs have changed a great deal since SDL's API was designed in the late 1990's. To deal with modern hardware and OS features, we have almost completely replaced the old 1.2 video API.

Don't worry, the new one is pretty great, and once you understand what's changed, you're going to be very happy with the new features it can bring to your 1.2 game. We'll discuss those later.

The good news: if your game used OpenGL, you probably don't have much to do: change a handful of function calls to their SDL2 equivalents, and you're good to go.

For 2D graphics, SDL 1.2 offered a concept called "surfaces," which were memory buffers of pixels. The screen itself was a "surface," if you were doing 2D software rendering, and we provided functions to copy ("blit") pixels between surfaces, converting formats as necessary. You were almost always working on the CPU in system RAM, not on the GPU in video memory. SDL 2.0 changes this; you almost always get hardware acceleration now, and the API has changed to reflect this.

If you have a 2D game, chances are you've taken one of three approaches to rendering. We'll go through them all, but first, let's talk about introductory stuff.

Remember SDL_SetVideoMode()? It's completely gone. SDL 2.0 allows you to have multiple windows, so the old function didn't make sense any more.

So you might have had something like this:

SDL_WM_SetCaption("My Game Window", "game");
SDL_Surface *screen = SDL_SetVideoMode(640, 480, 0, SDL_FULLSCREEN | SDL_OPENGL);

Which is now this:

SDL_Window *screen = SDL_CreateWindow("My Game Window",
                          SDL_WINDOWPOS_UNDEFINED,
                          SDL_WINDOWPOS_UNDEFINED,
                          640, 480,
                          SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);

You can see that this maps pretty closely to 1.2. The difference is that you can have multiple windows (if you want), and you can control them more. SDL_WM_SetCaption is gone, because we want to allow each window to have its own title (you can change it later with SDL_SetWindowTitle()), and we want to let you specify a window position (or, in this case, use SDL_WINDOWPOS_UNDEFINED since we don't care where the system places it. SDL_WINDOWPOS_CENTERED is also a good choice).

Extra credit for letting users specify a display for the window: SDL2 also allows you to manage systems with multiple monitors. Don't worry about that right now, though.

So now that your window is back on the screen, let's talk strategy. SDL2 still has SDL_Surface, but what you want, if possible, is the new SDL_Texture. Surfaces are always in system RAM now, and are always operated on by the CPU, so we want to get away from there. SDL2 has a new rendering API. It's meant for use by simple 2D games, but most notably, it's meant to get all that software rendering into video RAM and onto the GPU. And even if you just want to use it to get your software renderer's work to the screen, it brings some very nice benefits: if possible, it will use OpenGL or Direct3D behind the scenes, which means you'll get faster blits, a working Steam Overlay, and scaling for free.

The setup looks like this.

SDL_SetVideoMode() becomes SDL_CreateWindow(), as we discussed before. But what do we put for the resolution? If your game was hardcoded to 640x480, for example, you probably were running into monitors that couldn't do that fullscreen resolution at this point, and in windowed mode, your game probably looked like an animated postage stamp on really high-end monitors. There's a better solution in SDL2.

We don't call SDL_ListModes() anymore. There's an equivalent in SDL2 (call SDL_GetDisplayMode() in a loop, SDL_GetNumDisplayModes() times), but instead we're going to use a new feature called "fullscreen desktop," which tells SDL "give me the whole screen and don't change the resolution." For our hypothetical 640x480 game, it might look like this:

SDL_Window *sdlWindow = SDL_CreateWindow(title,
                             SDL_WINDOWPOS_UNDEFINED,
                             SDL_WINDOWPOS_UNDEFINED,
                             0, 0,
                             SDL_WINDOW_FULLSCREEN_DESKTOP);

Notice how we didn't specify 640 or 480...fullscreen desktop gives you the whole display and ignores any dimensions you specify. The game window should come up immediately, without waiting for the monitor to click into a new resolution, and we'll be using the GPU to scale to the desktop size, which tends to be faster and cleaner-looking than if an LCD is faking a lower resolution. Added bonus: none of your background windows are resizing themselves right now.

Now we need a rendering context.

SDL_Renderer *renderer = SDL_CreateRenderer(sdlWindow, -1, 0);

A renderer hides the details of how we draw into the window. This might be using Direct3D, OpenGL, OpenGL ES, or software surfaces behind the scenes, depending on what the system offers; your code doesn't change, regardless of what SDL chooses (although you are welcome to force one kind of renderer or another). If you want to attempt to force sync-to-vblank to reduce tearing, you can use SDL_RENDERER_PRESENTVSYNC instead of zero for the third parameter. You shouldn't create a window with the SDL_WINDOW_OPENGL flag here. If SDL_CreateRenderer() decides it wants to use OpenGL, it'll update the window appropriately for you.

Now that you understand how this works, you can also do this all in one step with SDL_CreateWindowAndRenderer(), if you don't want anything fancy:

SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(sdlRenderer);
SDL_RenderPresent(sdlRenderer);

This works like you might think; draw in black (r,g,b all zero, alpha full), clear the whole window, put the cleared window on the screen. That's right, if you've been using SDL_UpdateRect() or SDL_Flip() to get your bits to the screen, the render API uses SDL_RenderPresent().

One more general thing to set up here. Since we're using SDL_WINDOW_FULLSCREEN_DESKTOP, we don't actually know how much screen we've got to draw to. Fortunately, we don't have to know. One of the nice things about 1.2 is that you could say "I want a 640x480 window and I don't care how you get it done," even if getting it done meant centering the window in a larger resolution on behalf of your application.

For 2.0, the render API lets you do this...

SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");  // make the scaled rendering look smoother.
SDL_RenderSetLogicalSize(sdlRenderer, 640, 480);

...and it will do the right thing for you. This is nice in that you can change the logical rendering size to achieve various effects, but the primary use is this: instead of trying to make the system work with your rendering size, we can now make your rendering size work with the system. On my 1920x1200 monitor, this app thinks it's talking to a 640x480 resolution now, but SDL is using the GPU to scale it up to use all those pixels. Note that 640x480 and 1920x1200 aren't the same aspect ratio: SDL takes care of that, too, scaling as much as possible and letterboxing the difference.

Now we're ready to start drawing for real.

If your game just wants to get fully-rendered frames to the screen

A special case for old school software rendered games: the application wants to draw every pixel itself and get that final set of pixels to the screen efficiently in one big blit. An example of a game like this is Doom, or Duke Nukem 3D, or many others.

For this, you're going to want a single SDL_Texture that will represent the screen. Let's create one now for our 640x480 game:

sdlTexture = SDL_CreateTexture(sdlRenderer,
                               SDL_PIXELFORMAT_ARGB8888,
                               SDL_TEXTUREACCESS_STREAMING,
                               640, 480);

This represents a texture on the GPU. The gameplan is to finish each frame by uploading pixels to this texture, drawing the texture to the window, and flipping this drawing onto the screen. SDL_TEXTUREACCESS_STREAMING tells SDL that this texture's contents are going to change frequently.

Before you probably had an SDL_Surface for the screen that your app drew into, then called SDL_Flip() to put to the screen. Now you can create an SDL_Surface that is always in RAM instead of using the one you would have gotten from SDL_SetVideoMode(), or just malloc() a block of pixels to write into. Ideally you write to a buffer of RGBA pixels, but if you need to do a conversion, that's okay too.

SDL_UpdateTexture(sdlTexture, NULL, myPixels, 640 * sizeof (Uint32));

This will upload your pixels to GPU memory. That NULL can be a subregion if you want to mess around with dirty rectangles, but chances are modern hardware can just swallow the whole framebuffer without much trouble. The final argument is the pitch--the number of bytes from the start of one row to the next--and since we have a linear RGBA buffer in this example, it's just 640 times 4 (r,g,b,a).

Now get that texture to the screen:

SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);

That's all. SDL_RenderClear() wipes out the existing video framebuffer (in case, say, the Steam Overlay wrote over it last frame), SDL_RenderCopy() moves the texture's contents to the video framebuffer (and thanks to SDL_RenderSetLogicalSize(), it will be scaled/centered as if the monitor was 640x480), and SDL_RenderPresent() puts it on the screen.

If your game wants to blit surfaces to the screen

This scenario has your SDL 1.2 game loading a bunch of graphics from disk into a bunch of SDL_Surfaces, possibly trying to get them into video RAM with SDL_HWSURFACE. You load these once, and you blit them over and over to the framebuffer as necessary, but otherwise they never change. A simple 2D platformer might do this. If you tend to think of your surfaces as "sprites," and not buffers of pixels, then this is probably you.

You can build individual textures (surfaces that live in GPU memory) like we did for that one big texture:

sdlTexture = SDL_CreateTexture(sdlRenderer,
                               SDL_PIXELFORMAT_ARGB8888,
                               SDL_TEXTUREACCESS_STATIC,
                               myWidth, myHeight);

Which does what you'd expect. We use SDL_TEXTUREACCESS_STATIC, because we're going to upload our pixels once instead of over and over. But a more convenient solution might be:

sdlTexture = SDL_CreateTextureFromSurface(sdlRenderer, mySurface);

Use this, and you load your SDL_Surface as usual, but then at the end you make a texture out of it. Once you have an SDL_Texture, you can free the original surface.

At this point, your 1.2 game had a bunch of SDL_Surfaces, which it would SDL_BlitSurface() to the screen surface to compose the final framebuffer, and eventually SDL_Flip() to the screen. For SDL 2.0, you have a bunch of SDL_Textures, that you will SDL_RenderCopy() to your Renderer to compose the final framebuffer, and eventually SDL_RenderPresent() to the screen. It's that simple. If these textures never need modification, you might find your framerate has just gone through the roof, too.

If your game wants to do both

Things get slightly more complicated if you want to blit surfaces and modify individual pixels in the framebuffer. Round trips--reading data back from textures--can be painfully expensive; generally you want to be pushing data in one direction always. You are probably best off, in this case, keeping everything in software until the final push to the screen, so we'll combine the two previous techniques.

The good news: the 1.2 SDL_Surface API mostly still exists. So change your screen surface from this:

SDL_Surface *screen = SDL_SetVideoMode(640, 480, 32, 0);

...to this...

// if all this hex scares you, check out SDL_PixelFormatEnumToMasks()!
SDL_Surface *screen = SDL_CreateRGBSurface(0, 640, 480, 32,
                                        0x00FF0000,
                                        0x0000FF00,
                                        0x000000FF,
                                        0xFF000000);
SDL_Texture *sdlTexture = SDL_CreateTexture(sdlRenderer,
                                            SDL_PIXELFORMAT_ARGB8888,
                                            SDL_TEXTUREACCESS_STREAMING,
                                            640, 480);

...and continue blitting things around and tweaking pixels as before, composing your final framebuffer into this SDL_Surface. Once you're ready to get those pixels on the screen, you do this just like in our first scenario:

SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);

Note that texture creation may be both expensive and a limited resource: don't call SDL_CreateTextureFromSurface() every frame. Set up one texture and one surface and update the former from the latter.

There are more features to the Render API, some of which may be able to replace your application's code: scaling, line drawing, etc. If you are reading this section because you have simple needs beyond blitting surfaces, you might be able to stop poking individual pixels and move everything onto the GPU, which will give your program a significant speed boost and probably simplify your code greatly.

Other Renderer API notes

You can do some simple effects with the render API without having to get down into direct pixel manipulation. Some of these were available on 1.2 surfaces.

  • Color alpha: SDL_Color now contains a fourth, alpha component. Your 1.2 code that deals with SDL_Colors might be not copying/setting that value (which was named unused). In 2.0, you should.

  • Alpha blending: use SDL_SetSurfaceAlphaMod and SDL_SetTextureAlphaMod instead of SDL_SetAlpha(). Alpha-blending on surfaces can be disabled via SDL_SetSurfaceBlendMode() and on textures with SDL_SetTextureBlendMode().

  • Colorkey: When calling SDL_SetColorKey(), you should pass SDL_TRUE instead of SDL_SRCCOLORKEY.

  • Color modulation: Some renderers now support a global color alteration (srcC = srcC * color), check SDL_SetTextureColorMod() for details.

OpenGL

If you were already using OpenGL directly, your migration is pretty simple. Change your SDL_SetVideoMode() call to SDL_CreateWindow() followed by SDL_GL_CreateContext(), and your SDL_GL_SwapBuffers() call to SDL_GL_SwapWindow(window). All the actual calls into the GL are exactly the same.

If you had used SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, x), this has changed. There is now an SDL_GL_SetSwapInterval(x) call, so you can change this on an existing GL context.

Note that SDL 2.0 can toggle windowed/fullscreen and back with OpenGL windows without losing the GL context (hooray!). Use SDL_SetWindowFullscreen() for this.

Input

The good news is that SDL 2.0 has made Unicode input usable. The bad news is that it will take some minor changes to your application.

In 1.2, many applications that only cared about US English still called SDL_EnableUNICODE(1), because it was useful to get the character that was associated with a keypress. This didn't work well once you got outside of English, and it really didn't work at all once you got to Asian languages.

It turns out, i18n is hard.

SDL changed this. SDL_EnableUNICODE() is gone, and so is SDL_Keysym's unicode field. You no longer get character input from SDL_KEYDOWN events. Use SDL_KEYDOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.

The new event is SDL_TEXTINPUT. This is triggered whenever there's new text entered by the user. Note that this text might be coming from keypresses, or it might be coming from some sort of IME (which is a fancy way of entering complicated, multi-character text). This event returns entire strings, which might be one char long, or several codepoints of multi-character data. This string is always in UTF-8 encoding.

If all you care about is whether the user pressed a certain key, that's still SDL_KEYDOWN, but we've split this system into two pieces since 1.2: keycodes and scancodes.

Scancodes are meant to be layout-independent. Think of this as "the user pressed the Q key as it would be on a US QWERTY keyboard" regardless of whether this is actually a European keyboard or a Dvorak keyboard or whatever. The scancode is always the same key position.

Keycodes are meant to be layout-dependent. Think of this as "the user pressed the key that is labelled 'Q' on a specific keyboard."

In example, if you pressed the key that's two keys to the right of CAPS LOCK on a US QWERTY keyboard, it'll report a scancode of SDL_SCANCODE_S and a keycode of SDLK_S. The same key on a Dvorak keyboard, will report a scancode of SDL_SCANCODE_S and a keycode of SDLK_O.

Note that both keycodes and scancodes are now 32 bits, and use a wide range of numbers. There's no SDLK_LAST anymore. If your program had a lookup table of SDLK_LAST elements, to map between SDL keys and whatever your application wanted internally, that's no longer feasible. Use a hash table instead. A std::map will do. If you're mapping scancodes instead of keycodes, there's SDL_NUM_SCANCODES, which you can use for array bounds. It's 512 at the moment.

SDLMod is now SDL_Keymod and its "META" keys (the "Windows" keys) are now called the "GUI" keys.

SDL_GetKeyState() has been renamed to SDL_GetKeyboardState(). The returned array should now be indexed by SDL_SCANCODE_* values (see SDL_Scancode) instead of SDL_Keysym values.

Now, for mouse input.

The first change, simply enough, is that the mousewheel is no longer a button. This was a mistake of history, and we've corrected it in SDL 2.0. Look for SDL_MOUSEWHEEL events. We support both vertical and horizontal wheels, and some platforms can treat two-finger scrolling on a trackpad as wheel input, too. You will no longer receive SDL_BUTTONDOWN events for mouse wheels, and buttons 4 and 5 are real mouse buttons now.

If your game needed to roll the mouse in one direction forever, for example to let a player in an FPS to spin around without the mouse hitting the edge of the screen and stopping, you probably hid the mouse cursor and grabbed input:

SDL_ShowCursor(0);
SDL_WM_GrabInput(SDL_GRAB_ON);

In SDL2, this works slightly differently. You call...

SDL_SetRelativeMouseMode(SDL_TRUE);

...and SDL does the rest.

Events

SDL_PushEvent() now returns 1 on success instead of 0.

Events mask are now specified using ranges:

SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENTMASK(SDL_MOUSEBUTTONDOWN));

becomes:

SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONDOWN);

Audio

The good news for audio is that, with one exception, it's entirely backwards compatible with 1.2. If you want the new features, they're available to you, but you'll probably just compile and run without them.

That one really important exception: The audio callback does NOT start with a fully initialized buffer anymore. You must fully write to the buffer in all cases. If you don't have enough audio, your callback should write silence. If you fail to do this, you'll hear repeated audio, or maybe audio corruption. If you want to restore the old behavior of unconditionally initializing the buffer, just put an SDL_memset(stream, 0, len) at the start of your callback.

Joysticks

Joystick events now refer to an SDL_JoystickID. This is because SDL 2.0 can handle joysticks coming and going, as devices are plugged in and pulled out during your game's lifetime, so the index into the device list that 1.2 uses would be meaningless as the available device list changes.

To get an SDL_JoystickID for your opened SDL_Joystick*, call:

SDL_JoystickID myID = SDL_JoystickInstanceID(myOpenedStick);

And compare the joystick events' which field against myID. If you aren't using the event queue for joysticks, SDL_JoystickGetAxis() and friends work just like SDL 1.2.

You should also check out the new Game Controller API too, because it's cool, and maybe you did a lot of tap dancing with the 1.2 API that this new code would solve more cleanly. You can find it in SDL_gamecontroller.h. The Game Controller API integrates really nicely with Steam Big Picture Mode: you get automatic configuration of most controllers, and a nice UI if you have to manually configure it. In either case, Steam passes this configuration on to your SDL application.

Support for the older joystick API (/dev/input/js*) for Linux has been dropped from SDL2. SDL2 only supports the newer events API (/dev/input/event*) for joysticks. These events are not normally readable for normal user accounts, so even if joysticks are plugged in you will likely have none detected. This is something that end users will have to configure for themselves.

Threads

SDL_KillThread() is gone. It was never safe or reliable. The best replacement is to set a flag that tells a thread it should quit. That thread should check the flag with some frequency, and then the "killing" thread calls SDL_WaitThread() to clean up.

SDL_CreateThread() takes an extra parameter now, a name for the thread, which can be used by debuggers to identify it. If you don't care about that, just stuff an extra NULL into your function call.

Audio CDs

The 1.2 CD API is completely gone. There's no replacement. Chances are you aren't shipping your music as CD-Audio tracks on a disc at this point, if you're shipping a disc at all. You can use Ogg Vorbis or some other audio file format for music, many of which are provided by SDL_mixer.

Dead platforms

We ripped out a bunch of old platforms, like OS/2 and Mac OS 9. It would be easier to list the ones we still support: Windows (XP and later), Linux, Mac OS X, iOS, Android. In SDL tradition, there are others on the periphery that work but aren't heavily supported, like Haiku and Sony PSP. We'll add any platform that someone sends patches for, but it seemed like it was time to say goodbye to some old friends when moving to the new version.

Mobile platforms

There have been, for many years, unofficial ports of SDL 1.2 to iOS and Android. SDL now supports these platforms directly, and the 2.0 API is much better suited to them. Most of the advice you've gotten elsewhere in this document applies, but there are a few other things worth noting.

First, there are certain events that only apply to mobile devices, or better said, apply to the way mobile device OSes tend to operate in a post-iPhone world. We originally tried to map these to the existing SDL events (such as "your application is going to the background" being treated like a desktop window losing focus), but there's a more urgent concern: most of these events need an immediate response, and if the app doesn't give one, the OS will kill your application.

As such, we've added new SDL events for some Android and iOS specific details, but you should set up an SDL event filter to catch them as soon as the OS reports them, because waiting until your next SDL_PollEvent() loop will be too late.

For example, there's SDL_APP_WILLENTERBACKGROUND, which is iOS's applicationWillResignActive(), and if you draw to the screen after this event arrives, iOS terminates your process. So you want to catch this immediately:

int SDLCALL myEventFilter(void *userdata, SDL_Event * event)
{
    if (event->type == SDL_APP_WILLENTERBACKGROUND) {
        // free up resources, DON'T DRAW ANY MORE until you're in the foreground again!
    }
    // etc
    return 1;
}

// somewhere near startup...

// this calls myEventFilter(data, event) as soon as event is generated.
SDL_AddEventWatch(myEventFilter, data);

Second, there are real touch events now, instead of trying to map this to mouse input. You can track touches, multiple fingers, and even complex gestures. You probably want to use those. Refer to SDL_touch.h for a list of these functions, and look for SDL_Finger* in SDL_events.h.

There are a handful of other mobile-friendly functions, like SDL_StartTextInput(), which will show the on-screen keyboard. Make use of them.

In addition, there are also Android and iOS specific functions, to let you access platform-specific features that wouldn't make sense in a general API. Refer to SDL_system.h for a list of these functions.

RWops

SDL_RWread() and SDL_RWwrite() now return 0 on error instead of -1.

If you wrote your own SDL_RWops implementation, the function signatures have changed. Functions now use Sint64 and size_t instead of int so they can work with large files. In many cases, you can just update your function signatures and keep working as before, but if you had bumped up against these limitations, you might be happy to have a solution. Calling applications should know that the return values have changed.

There is also a size method to RWops, now. It is called SDL_RWsize(). This lets a RWops report the size of the stream without having to make the app seek to zero bytes from the end; in other words, you can report a total size for streams that can't seek. For streams that can't even do that, you can still return -1.

Add-on libraries

The official extensions SDL_image, SDL_ttf, SDL_mixer and SDL_net have a version dedicated to SDL 2.0 : SDL2_image, SDL2_ttf, SDL2_mixer and SDL2_net. You may need to download them from the mercurial repositories for the latest fixes. Subsequently, of course, you will have to link e.g. SDL2_image, not SDL_image, to compile your program.

These libraries will not be supporting 1.2 going forward, and any compatibility with 1.2 is likely to vanish at some point from newer versions.

SDL_gfx can also be compiled with 2.0 starting since 2.0.21 (May 2010).

Summary of some renamed or replaced things

A short cheat sheet where some of the old functions and other stuff went:

  • SDL_SetVideoMode(): use
            SDL_CreateWindow()
  instead (along with SDL_CreateRenderer() if
  you want to do classic 2D rendering and not OpenGL)

  • SDL_ListModes(): use SDL_GetDisplayMode()/SDL_GetNumDisplayModes() instead

  • SDL_UpdateRect()/SDL_Flip(): use SDL_RenderPresent() instead

  • SDL_Surface/2D rendering: surfaces still exist, but it is recommended that instead of using SDL_Surfaces, you use SDL_Textures with an 2D accelerated renderer (SDL_CreateRenderer()) where possible

  • SDL_VideoInfo: use SDL_GetRendererInfo()/SDL_GetRenderDriverInfo() instead

  • SDL_GetCurrentVideoDisplay(): use SDL_GetWindowDisplayIndex() instead

  • SDL_VIDEORESIZE event: the new equivalent is SDL_WINDOWEVENT_RESIZE

Other stuff

There's an enormous amount of new and interesting functionality in SDL 2.0 that 1.2 couldn't even dream of. We've only tried to explain what you might have to do to get your 1.2 program running on 2.0 here, but you should explore the documentation for things that you might have always wished for and, until now, done without. For example, every game I've ever ported ended up with a message box function that looked like this:

#if USING_SDL
fprintf(stderr, "MSGBOX: %s\n%s\n", title, text);   // oh well.
#endif

Now there's SDL_ShowSimpleMessageBox(). You're welcome!

If you skipped ahead, go back and check out all the new features at the overview!

None: MigrationGuide (last edited 2015-01-02 21:37:32 by PhilippWiesemann)

Feedback
Please include your contact information if you'd like to receive a reply.
Submit
  • MoinMoin Powered
  • Python Powered
  • GPL licensed
  • Valid HTML 4.01


Exemple

SDL Game Series

  • SDL Game Framework Series
  • SDL Tutorial Basics
  • SDL Coordinates and Bliting
  • SDL Events
  • SDL Tutorial - Tic Tac Toe
  • SDL Animation
  • SDL Entities
  • SDL Maps
  • SDL Collision
  • SDL Collision Events
 b SDL tutoriel en

SDL Tutorials

  • SDL Tutorials
  • SDL Image
  • SDL SoundBank
  • SDL Sokoban
  • SDL Scale Surface
  • SDL Net - Part 1
  • SDL Net - Part 2
  • SDL Per Pixel Collision
  • SDL + OpenGL Tutorial Basics
  • SDL TTF
  • SDL App States


SDL Tutorials

Category

SDL App States

Tim Jones | October 19th, 2011 | 28 comments
In this muchly desired side tutorial we are going to look at how to implement something most commonly called Game States (or as I call them, App States). This tutorial will be based upon the code from the SDL Collision Events tutorial.

To simply explain what an app state is, try to think of your game (or application) as a parent with many child applications. In other words, your primary application can yield control to a single child when needed.
SDL Tutorials
App States
Game States

SDL TTF

Alexander Bock | September 8th, 2011 | 12 comments
Text is an important part of almost all games. From informing the player of the awesome combo that they just got, to menu titles, button captions and epic storylines. In this tutorial, we will take a look at SDL_ttf, an addon library for SDL that uses the FreeType library to load fonts and render text.
SDL Tutorials
Fonts

SDL + OpenGL Tutorial Basics

Tim Jones | June 5th, 2010 | 50 comments
In this new tutorial series, we will be looking at using SDL along with OpenGL. SDL as it stands is nice for basic 2D graphics/composition, but when i
SDL Tutorials
SDL + OpenGL Tutorials

SDL Per Pixel Collision

Jason Norris | January 28th, 2010 | 22 comments
Are bounding box and circle collisions just not cutting it? Well, now we're going to dive right down into pixel precision!

Before we start, I'd like to say that this is an advanced SDL tutorial. You should have experience with creating SDL applications and some basic knowledge of getting a program with it's libraries up and running.

First, we'll start off with a very simple Game class to build the foundation of our demo project.
SDL Tutorials

SDL Net - Part 1

Tim Jones | January 7th, 2010 | 5 comments
The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of
SDL Tutorials

SDL Net - Part 2

Tim Jones | January 7th, 2010 | 12 comments
The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of
SDL Tutorials

SDL Scale Surface

Tim Jones | July 15th, 2008 | 14 comments
The following user tutorial was created by Mod J., for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL.
SDL Tutorials

SDL Sokoban

Tim Jones | June 9th, 2008 | 6 comments
The following user tutorial was created by Arseniy B., for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of S
SDL Tutorials

SDL SoundBank

Tim Jones | April 11th, 2008 | 25 comments
In this side tutorial we are going to be adding a soundbank that will load all of our sounds, and then we call play them via an ID whenever we want. T
SDL Tutorials

SDL Image

Tim Jones | March 14th, 2008 | 76 comments
This side tutorial is rather simple, short, and sweet. I am going to show you how to stop using those pesky bitmap (BMP) files that are too big and do
SDL Tutorials
RECENT TUTORIALS

First Foray into SDL/OpenGL

OpenGL handles the drawing of models in a window but it does not provide the window, or handle events; that is why SDL is required. The prime purpose of this tutorial is to set up the SDL window and events mechanism. It also sets up the OpenGL environment that uses the window, the game loop, and a rotating cube.

Huh? My Pong clone uses 100% CPU and is still slow?

Your program is being a CPU hog! In some cases, this situation can actually make your program run slowly! Your code needs to give some time back to the OS (i.e. delay or sleep).

Setting up MonoDevelop to run Tao

A new series from Stephen Jones demonstrating how to use C#, Mono, and Tao to develop SDL / OpenGL games.


SDL Image
This side tutorial is rather simple, short, and sweet. I am going to show you how to stop using those pesky bitmap (BMP) files that are too big and don't support alpha transparency, and to start using other file formats for you surfaces (I personally like PNG). If you have not read my SDL Coordinates and Bliting tutorial, I encourage you to do so now. We will be building off of that tutorial, modifying the OnLoad function of the CSurface class.

The first thing you need to do is download SDL_image, the latest version, from the main SDL website. You can also download this library from the "Libraries" section of this website, under SDL. If you don't want to worry about finding all these libraries, the SDL package I provide is good for most SDL beginners, as it provides SDL_image, and some other useful libraries. Be sure to put the include files in the same directory as your SDL include files, and your lib files in the same directory as your SDL lib files to make things easier.

Start by opening your project, and going under the linker settings. Add SDL_image after SDLmain, something like:

mingw32
SDLmain
SDL_image
SDL

If you don't remember where to find all these settings, hope on over to the first tutorial SDL Tutorial Basics to get a refresher on linking.

First, open up CSurface.h, so that we can include an extra header file:
#ifndef _CSURFACE_H_
    #define _CSURFACE_H_

#include <SDL.h>
#include <SDL_image.h>
Once that is all done, open up CSurface.cpp and look for the OnLoad function. Currently, this is what our code looks like:
SDL_Surface* CSurface::OnLoad(char* File) {
    SDL_Surface* Surf_Temp = NULL;
    SDL_Surface* Surf_Return = NULL;

    if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
        return NULL;
    }

    Surf_Return = SDL_DisplayFormat(Surf_Temp);
    SDL_FreeSurface(Surf_Temp);

    return Surf_Return;
}
There are two changes to make here, the first is changing SDL_LoadBMP to IMG_Load. The second is changing SDL_DisplayFormat to SDL_DisplayFormatAlpha. Remember I said BMPs don't support alpha layers? Well, PNGs do! And if you are wanting to keep that alpha layer you'll need to use SDL_DisplayFormatAlpha instead of SDL_DisplayFormat. So, our code should look like this now:
SDL_Surface* CSurface::OnLoad(char* File) {
    SDL_Surface* Surf_Temp = NULL;
    SDL_Surface* Surf_Return = NULL;

    if((Surf_Temp = IMG_Load(File)) == NULL) {
        return NULL;
    }

    Surf_Return = SDL_DisplayFormatAlpha(Surf_Temp);
    SDL_FreeSurface(Surf_Temp);

    return Surf_Return;
}
Finally, don't forget to change the filename that the Test Surface is trying to load in CApp_OnInit.cpp:
if((Surf_Test = CSurface::OnLoad("myimage.png")) == NULL) {
    return false;
}
And we are done! I told you it would be short. Try compiling, and trying out different file formats. Some common formats supported by SDL_image are:

BMP (Go figure)
GIF
PNG
JPG
PCX
TIF
and others...

Please note that there are additional DLL files associated with SDL image that are needed now (about 5 of them). So be sure to include those with your project.

You can download these DLL files here, http://www.libsdl.org/projects/SDL_image/ under Binary -> Win32 -> and the Win32 zip (not the VC8). Also, the DLL files are also included in the project files below.


SDL SoundBank
In this side tutorial we are going to be adding a soundbank that will load all of our sounds, and then we call play them via an ID whenever we want. This tutorial will only deal with sounds, not music, and mind you it's a very basic tutorial that gets the job done. There is much that can be added to this class, for channels, groups and such, but we're dealing with basics here. We'll base this tutorial off of my SDL Events tutorial. So use those project files if you need something to work off of.

The first thing you need to do is download SDL mixer from the SDL website. It's also included in the SDL library I provide on my website. Be sure to put the include files in the same directory as your SDL include files, and your lib files in the same directory as your SDL lib files to make things easier.

Start by opening your project, and going under the linker settings. Add SDL_mixer after SDLmain, something like:

mingw32
SDLmain
SDL_mixer
SDL

If you don't remember where to find all these settings, hope on over to the first tutorial SDL Tutorial Basics to get a refresher on linking. If you have a .lib file, instead of a .a library file, use the browse button to find the library file that way.

Just a little side note here about sounds. SDL_mixer treats any sound in memory as a chunk. This chunk basically contains the frequency data. What's nice about SDL_mixer is that it takes this data and sends it to the audio output, meaning it has taken care of all the hard work. So all that we have to do is load our data, from a WAV file for instance, and then send it to SDL_mixer to be played.

First, create two new files, CSoundBank.h and CSoundBank.cpp. Open up the header file first:
#ifndef _CSOUNDBANK_H_
#define _CSOUNDBANK_H_
#include <SDL.h>
#include <SDL_mixer.h>
#include <vector>

class CSoundBank {
    public:
        static CSoundBank           SoundControl;

        std::vector<Mix_Chunk*>     SoundList;

    public:
        CSoundBank();

        int OnLoad(char* File);

        void OnCleanup();

    public:
        void Play(int ID);
};

#endif
We have a static control object first, this lets us add sounds and play sounds from anywhere in our program. Think of this as your master control for adding/playing sounds. We then have our SoundList, this is a list of SDL mixer sounds. Now, a Mix_Chunk object contains the information needed to play a sound. What we will do later on is load a wav file into a Mix_Chunk object. Think of it as the sound equivalent to SDL_Surface. Then we have some basic functions for loading, cleanup up, and playing a sound. Simple enough.

Next, open up CSoundBank.cpp:
#include "CSoundBank.h"

CSoundBank CSoundBank::SoundControl;

CSoundBank::CSoundBank() {
}

int CSoundBank::OnLoad(char* File) {
    Mix_Chunk* TempSound = NULL;

    if((TempSound = Mix_LoadWAV(File)) == NULL) {
        return -1;
    }

    SoundList.push_back(TempSound);

    return (SoundList.size() - 1);
}

void CSoundBank::OnCleanup() {
    for(int i = 0;i < SoundList.size();i++) {
        Mix_FreeChunk(SoundList[i]);
    }

    SoundList.clear();
}

void CSoundBank::Play(int ID) {
    if(ID < 0 || ID >= SoundList.size()) return;
    if(SoundList[ID] == NULL) return;

    Mix_PlayChannel(-1, SoundList[ID], 0);
}
This is also pretty straight forward. We have our static control, our empty constructor, and then our loading function. We basically take the filename of the sound to be loaded, and try to load it via Mix_LoadWAV into the Mix_Chunk object. Notice I am making a temporary object first, and then throwing it into the list. At the very end of the function, I return the ID of the last inserted sound. So, for instance, say I loaded SoundA and SoundB:
int SoundA = CSoundBank::SoundControl.OnLoad("sounda.wav");
int SoundB = CSoundBank::SoundControl.OnLoad("soundb.wav");
The ID for playing this sounds later would be returned by the OnLoad function.

Okay, next we have our cleanup function which goes through the list and frees the sound chunks. Simple enough, just like SDL_FreeSurface.

Lastly, we have our Play function that actually plays the sound. We pass an ID (the one we got returned from the Load function) first. We do a little boundary check to make sure the ID is valid, and then actually try to play the sound. The Mix_PlayChannel takes three arguments:

The first specifies which channel to play the sound on, -1 means use the first available.
The second specifies which sound chunk to play.
The last specifies how many times to play this sound (loop). -1 would be infinite.

We haven't done much with channels in this class, but the class is easily changeable to add that functionality. Basically, a channel allows you to set certain settings to certain sounds. For instance, you can set volume or panning on the first channel, so any sounds played on that channel would be affected.

So lets do a little test now. Open up CApp.h and add the header file, and add some variables and functions to the class:
#include "CSoundBank.h"

//...

class CApp {
    public:
        int SoundA;
        int SoundB;

        //... other code

    public:
        void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);
};
Now, open up CApp_Init.cpp so we can load some sounds:
if((SoundA = CSoundBank::SoundControl.OnLoad("sounda.wav")) == -1) {
    return false;
}

if((SoundB = CSoundBank::SoundControl.OnLoad("soundb.wav")) == -1) {
    return false;
}
Now, to make sounds when we press a key, open up CApp_OnEvent.cpp and add the function:
void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    if(sym == SDLK_1) {
        CSoundBank::SoundControl.Play(SoundA);
    }

    if(sym == SDLK_2) {
        CSoundBank::SoundControl.Play(SoundB);
    }
}
We aren't done yet, we have to do a little bit to start SDL_mixer. This is just like starting up SDL, but instead we need to start up SDL_mixer to access sounds. Open up CApp_Init.cpp and add the following somewhere after SDL_Init:
if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) {
    return false;
}
Mix_OpenAudio basically Inits SDL_mixer. This function takes 4 arguments. The first is used to specify the frequency, 44100 is usually a good number. But depending on your wav files, you may have to change this. Another common number is 22050. I won't get into detail about frequencies and such, just leave it alone unless you really need to change it. The next is the format, again, it's best to leave it unless you know much about audio (it's basically the size of the samples used, 8bit or 16bit). The next is the number of channels, 1 = mono, 2 = stereo. Note, these channels are different than the other channels I was talking about before. The other channels are "mixing channels," basically streams that I can push audio through. These channels though, are the number of audio outputs. Think of it like the number of speakers that you might have. 2 speakers, 2 channels. The last number is used to set the size of the samples (in bytes). Again, unless you really know what you are doing, it's best to leave these numbers alone.

To tidy things up, we need to make sure we stop the SDL_mixer service when we are done, as well as freeing any sounds we may have loaded. So open up CApp_OnCleanup.cpp and add the following:
CSoundBank::SoundControl.OnCleanup();

Mix_CloseAudio();
And we are done! Try compiling, and pressing the 1 and 2 keys to here some sounds. If you need some WAV files, download the project files below to grab them.


SDL Sokoban
The following user tutorial was created by Arseniy B., for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.
IMPORTANT NOTE: I DON'T NAME SURFACES LIKE TIM DOES. WHEN HE CALLS IT Surf_Display, MINE IS SIMPLY SurfDisplay, WITHOUT THE UNDERSCORE. MAKE SURE YOU KNOW THIS, AND ADAPT TO THE TUTORIAL ACCORDINGLY. ALSO, WHEN I MAKE PROTOTYPES IN CLASS DEFINITIONS, THE ONLY STUFF I WRITE IN THE PARAMETERS AREA (I.E. THE PARENTHESES) IS THE TYPE OF DATA TAKEN, NOT THE NAME OF THE DATA TAKEN. THAT NAME IS ONLY IN THE ACTUAL FUNCTION DEFINITION, AT LEAST FOR ME.

This tutorial is based on everything you've learned by now, if you've been following the tutorials in order and have just finished the Maps tutorial. Get ready to get a glimpse of creating your first real game! (Not counting that fabulous tic-tac-toe.) Similarly, the code this comes from is from all the code you've written up to the Maps tutorial, and including it. It would be wise to make a new Code::Blocks project (or a new DevC++ project, or whatever), because this tutorial will delve a different way for a bit, and you don't want to mess up your existing code for the upcoming Collision Detection tutorial. Speaking of that, this tutorial will introduce you to very simple collision detection, on a tile-based board.

If you're all ready, open up your [new] project and let's begin!

First, we need an introduction to Sokoban. For those of you who have never played this unusually fun and addicting puzzle game, here's how it goes. There's a player that you control with the arrow keys, and he pushes boxes on tiles to their 'home' locations. Those locations are special tiles on the board. Once all the boxes are at 'home', the level ends. The challenge, though, is that the boxes are positioned initially in such a way that makes it a puzzle. A box can't be pushed into a wall, or out of a corner, etc., so it becomes harder. That's all there is to it!

Here we go. This tutorial, like the last few, will only instruct you as to where to put the code I write; there will be no full documents, unless they're new. First and foremost, we need to modify CTile. Add another element to the typedef enum statement at the top.

typedef enum {TILE_TYPE_NONE = 0, TILE_TYPE_NORMAL, TILE_TYPE_BLOCK, TILE_TYPE_HOMEOFBLOCK};
You'll see why soon.

Also, we need to make a new function in the CMap class that checks what the TypeID of a given tile is. Add the appropriate prototype in the class definition, and add this function in CMap.cpp:

int CMap::TypeOfTileAt(int X, int Y) {
    int CheckID = (X / TILE_SIZE) + ((Y / TILE_SIZE) * MAP_WIDTH);
    return TileList[CheckID].TypeID;
}
Simple enough.

Sokoban needs blocks. Let's make a block! The CBlock class is going to extend CEntity. We're going to make this new class for the block inherit all the attributes (variables) and methods (functions) of the CEntity class. It also has its own attributes and methods that don't exist in the CEntity class alone. A good example to illustrate this is geometry. The 'parent' class is the Shape class. It has the attributes of area, color, and perimeter. A child is Square, which has those same attributes due to inheritance, but also has side (because it's a square), and Shape doesn't have side because it could be a shape with no straight lines. Circle is also a child, because it inherits area, color, and perimeter, but it has a radius attribute for itself; etc.

So open up two new files, CBlock.h and CBlock.cpp.
#ifndef CBLOCK_H_INCLUDED
#define CBLOCK_H_INCLUDED
#include "CEntity.h"
#include "CArea.h"
#include <vector>

using namespace std;

class CBlock : public CEntity {
    public:
        static vector<CBlock*> BlockList;
        bool OnHomeTile;
        CBlock();
        void CheckForHome();
        void OnMove(int, int);
};

#endif // CBLOCK_H_INCLUDED
And...
#include "CBlock.h"

vector<CBlock*> CBlock::BlockList;

CBlock::CBlock() {
    OnHomeTile = false;
    SurfEntity = NULL;
    X = Y = 0.0f;
    Width = Height = 32;
    AnimState = 0;
}

void CBlock::CheckForHome() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y) == 3) {
        OnHomeTile = true;
    } else {
        OnHomeTile = false;
    }
}

void CBlock::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}

After the class definition, we have " : public CEntity." This is what tells the compiler that this new class extends and inherits the attributes and methods of the CEntity class. This class, much like the CEntity class, has its own vector of CBlocks. Sure, it makes sense to just use the CEntity::EntityList vector in its place, but we're going to need a vector of just CBlocks (not including the CEntity that will be CPlayer).

Next, CBlock has an attribute that says whether or not it's located on its 'home' tile. The constructor, CBlock(), sets that value to false, and the rest of the values which it inherits from its parent class, CEntity, are set to their default values, as well. The next function, CheckForHome(), sets the OnHomeTile boolean value to true, if the TypeID of the tile under the block is TILE_TYPE_HOMEOFBLOCK. Otherwise, it's false. There isn't a return statement because this function just deals with CBlock's personal variables. The final function, OnMove, is very simple. It takes in two integer variables, MoveX and MoveY, and increments the block's X and the block's Y by those amounts, respectively. (Remember, even though the class didn't set an X or a Y variable, CBlock inherits those two from CEntity, so they're allowed to be manipulated. And they exist.)

Next, we need to implement a player, in kinda the same way.

CPlayer, a child of CEntity, will inherit every one of its attributes and methods, and we will define our own and maybe even override the inherited methods. Make two new files, CPlayer.cpp and CPlayer.h, and open up CPlayer.h.

#ifndef CPLAYER_H_INCLUDED
#define CPLAYER_H_INCLUDED
#include "CEntity.h"
#include "CBlock.h"
#include "CArea.h"

typedef enum {YES = 0, NO, BLOCK};

class CPlayer : public CEntity {
    public:
        CPlayer();
        void OnMove(int MoveX, int MoveY);

        int CanPlayerMoveLeft();
        int CanPlayerMoveUp();
        int CanPlayerMoveRight();

        int CanPlayerMoveDown();
};

#endif CPLAYER_H_INCLUDED
Then, open CPlayer.cpp and let's define the functions.

#include "CPlayer.h"

CPlayer::CPlayer() {
    SurfEntity = NULL;
    X = Y = 0.0f;
    Width = Height = 0;
    AnimState = 0;
}

void CPlayer::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}

int CPlayer::CanPlayerMoveLeft() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X - 32, Y) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->X == X - 32 && CBlock::BlockList[i]->Y == Y) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X - 64, Y) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y && CBlock::BlockList[k]->X == X - 64) return NO;
            }

            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveUp() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y - 32) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->Y == Y - 32 && CBlock::BlockList[i]->X == X) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y - 64) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y - 64 && CBlock::BlockList[k]->X == X) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveRight() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X + 32, Y) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->X == X + 32 && CBlock::BlockList[i]->Y == Y) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X + 64, Y) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y && CBlock::BlockList[k]->X == X + 64) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}

int CPlayer::CanPlayerMoveDown() {
    if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y + 32) == 2) return NO;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        if (CBlock::BlockList[i]->Y == Y + 32 && CBlock::BlockList[i]->X == X) {
            if (CArea::AreaControl.MapList[0].TypeOfTileAt(X, Y + 64) == 2) return NO;
            for (int k = 0; k < CBlock::BlockList.size(); k++) {
                if (CBlock::BlockList[k]->Y == Y + 64 && CBlock::BlockList[k]->X == X) return NO;
            }
            return BLOCK;
        }
    }

    return YES;
}
A little explanation. Just like with CBlock, " : public CEntity" is to extend the CEntity class.

In the CPlayer class, we're defining five new functions, OnMove and the four "can he move in a certain direction" functions. OnMove is simple: two integers are passed, and the CPlayer's X and Y coordinates increment by exactly that much. (Remember, CPlayer has the X and Y attributes, even though they weren't defined in CPlayer.h, because he INHERITED them from CEntity.) Here's where it gets a little trickier. Those four other functions are the collision detection functions. They check whether the player can move left, up, right, or down, and return what the result of the check was. So let's examine CanPlayerMoveLeft() closely.

First, look at the top of CPlayer.h. The typedef enum statement, like others before, makes YES equal to 0, NO equal to 1, and BLOCK equal to 2; this is because it's much easier to remember YES, NO, and BLOCK, than it is to remember that 0 is the same thing as YES, etc.

The function first checks if the TypeID of the tile to the left of the player is TILE_TYPE_BLOCK. If so, then the function returns NO, meaning the player cannot move left (because a wall is blocking his movement). Then, the function checks if there's a block there. It does this by looping through every one of the CBlocks in the static BlockList (which are automatically added when the CBlocks are loaded...we'll get to that a bit later). Every time it loops, it checks the X and Y coordinates of the block, and if a block is to the left of the player, then the function returns BLOCK, which will tell the program that the player has a block to the left of him. There are two caveats, though. First, if there is a block to the left of the player, but there is also a TILE_TYPE_BLOCK (i.e. a wall) to the left of that block, the player cannot move, and the function returns NO. Similarly, second, if there are two blocks to the left of him, the player can't move (he's not THAT strong!), and the function returns NO. Otherwise, it returns BLOCK. However, if NONE of those are true, then the function returns a simple YES, because the player has a free spot to his left.

That repeats for each direction. The purpose of this function is so that, when the OnKeyDown() function is called (in CApp), before moving the character we test if he can move. That said, let's move on to the implementation of these functions.

We need a way to load the blocks. Just like with the maps and areas, we're going to have a directory next to your exe called blocks, with (so far) one file in it: 1.blocks. Make that file, position it accordingly in the directory tree, and open it. Our first level will have blocks at these coordinates:

(160, 128) (160, 192) (224, 160) (224, 192) (160, 288) (64, 288)

Make two more new files called CBlockControl.h and CBlockControl.cpp, and open up the header.
#ifndef CBLOCKCONTROL_H_INCLUDED
#define CBLOCKCONTROL_H_INCLUDED
#include "CBlock.h"
#include <vector>

using namespace std;

class CBlockControl {
    public:
        static vector<CBlock> BlockList;
        static bool LoadBlocksFromFile(char*);
};

#endif // CBLOCKCONTROL_H_INCLUDED
And...
#include "CBlockControl.h"

vector<CBlock> CBlockControl::BlockList;

bool CBlockControl::LoadBlocksFromFile(char* File) {
    FILE* FileHandle = fopen(File, "r");

    if (FileHandle == NULL) {
        return false;
    }

    int BlockCount;

    fscanf(FileHandle, "%d\n", &BlockCount);

    for (int i = 0; i < BlockCount; i++) {
        CBlock TmpBlock;
        int X, Y;
        fscanf(FileHandle, "%d ", &X);
        fscanf(FileHandle, "%d\n", &Y);
        TmpBlock.X = X;
        TmpBlock.Y = Y;
        TmpBlock.OnLoad("gfx/block.png", 32, 32, 1, false, 64, 64, 0);
        CBlockControl::BlockList.push_back(TmpBlock);
    }

    fclose(FileHandle);

    return true;
}
This is a class that will load every one of the blocks and their locations from a text file (namely, blocks/x.blocks, where x is the level number). When the static function is called, it takes in a char array. As we've done before with Tim's tutorials, it reads that file. That file contains a number (in level one's case, 6), followed by a next-line/return character. That number is stored in the integer BlockCount. Then, the function loops through every one of the blocks in the file (with the for loop), and loads their respective X and Y coordinates. Inside the for loop, a TmpBlock (TeMPoraryBLOCK) is created and its X and Y coordinates (attributes set by CEntity, and inherited by the CBlock class, remember) are set as the values in the X and Y integers in the for loop. Then, the TmpBlock is inserted into CBlockControl's static vector of CBlocks (CBlockControl::BlockList).

So, it seems that now we have everything needed to actually implement everything we've just written; to bring it all together and make a level. Well, first we have to accomodate for one slight change. There is no longer a CCamera. We don't need one as the map can't get bigger than the one level. So we have to change something in CArea. Remember the function CArea::OnRender(SDL_Surface* SurfDisplay, int CameraX, int CameraY)? Well since we don't need a camera anymore, we don't need that function anymore. But don't toss it! Who knows, we might later need the function for any additions you decide to make. So let's make a new function:

void CArea::OnRender(SDL_Surface* SurfDisplay);

Add the prototype to the class definition (make sure it's public), and write the function in CArea.cpp:

void CArea::OnRender(SDL_Surface* SurfDisplay) {
    MapList[0].OnRender(SurfDisplay, 0, 0);
}
We now have two functions with the same name, but they have different parameters, which is allowed. It does the same thing, except there's one map to render so there's no need to figure out which is the first map to render. Duh.

Finally, open up CApp. We're gonna put this thing together. In CApp.h, add the following things:

private:
    SDL_Surface* SurfBackground;
    CPlayer ActionJackson;
    int Moves;
public:
    void OnKeyDown(SDLKey, SDLMod, Uint16);
Remember, this isn't the whole thing; this is just what you need to add in the appropriate places. I think you already have the new function, but make sure it's there, in any case.

So then, in CApp.cpp... In the constructor, CApp::CApp(), add statements that define Moves as 0, and SurfBackground as NULL (like SurfDisplay). In CApp::OnInit(), we need to almost completely rewrite it:

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if ((SurfDisplay = SDL_SetVideoMode(WWIDTH, WHEIGHT, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    if ((SurfBackground = CSurface::OnLoad("gfx/background.png")) == false) {
        return false;
    }

    if (CArea::AreaControl.OnLoad("maps/1.area") == false) {
        return false;
    }

    if (ActionJackson.OnLoad("gfx/actionjackson.png", 32, 32, 1, false, 64, 64, 0) == false) {
        return false;
    }

    CEntity::EntityList.push_back(&ActionJackson);

    if (CBlockControl::LoadBlocksFromFile("blocks/1.blocks") == false) {
        return false;
    }

    for (int i = 0; i < CBlockControl::BlockList.size(); i++) {
        CBlock::BlockList.push_back(&CBlockControl::BlockList[i]);
        CEntity::EntityList.push_back(&CBlockControl::BlockList[i]);
    }

    ActionJackson.X = 352;
    ActionJackson.Y = 320;

    return true;
}

Let's talk about this. The first two if's start SDL, like we know already. The third is just something I thought would look nice. I have this cool red graphic that I made, and I put it in gfx, and called it background.png. This statement uses our CSurface function OnLoad and puts it on SurfBackground. Next, an area is loaded. I'll get to what's in that area file in a second. The next statement loads the player. Remember, CPlayer inherits everything from CEntity, and since CEntity had an OnLoad function that loaded the entity from an image file, so does CPlayer. And thus, ActionJackon (which we declared in the class definition) has it. NOTE: MY PARAMETERS MAY BE DIFFERENT FROM YOURS, SO CHANGE THEM AS IS NECESSARY. THERE'S NO NEED FOR TRANSPARENCY, AT LEAST NOT YET, SO JUST REMEMBER THAT.

ActionJackson is pushed back in the static EntityList vector, and then CBlockControl loads all the blocks from a file. Remember, it's static, so we can run it without having declared an object/instance of CBlockControl. Then, like I promised WAAAAAY back, CBlock's static BlockList vector would get those blocks. And, like we need to do and like we did for ActionJackson, we need to push the blocks back in EntityList, too. Finally, we just need to set initial X and Y coordinates for ActionJackson, and that's all for OnInit().

Go to OnRender(). Rewrite it as this:

void CApp::OnRender() {
    CSurface::OnDraw(SurfDisplay, SurfBackground, 0, 0);

    CArea::AreaControl.OnRender(SurfDisplay);

    for (int i = 0; i < CEntity::EntityList.size(); i++) {
        if (!CEntity::EntityList[i]) continue;
        CEntity::EntityList[i]->OnRender(SurfDisplay);
    }

    SDL_Flip(SurfDisplay);
}
Since we loaded a background to SurfBackground, we have to draw SurfBackground on SurfDisplay. And this MUST be the first render statement because otherwise, the background will be drawn OVER the other things. Also, the new function we created for CArea is called, taking only the filename and no camera coordinates (since we no longer need a camera).

Finally, the meat of the game. Go to CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode). Erase all that stuff involving camera movement, and rewrite the function as this:

void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_UP:
            if (ActionJackson.CanPlayerMoveUp() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X && CBlock::BlockList[i]->Y == ActionJackson.Y - 32) {
                        CBlock::BlockList[i]->OnMove(0, -32);
                    }
                }
                ActionJackson.OnMove(0, -32);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveUp() == YES) {
                ActionJackson.OnMove(0, -32);
                Moves++;
            }
            break;

        case SDLK_DOWN:
            if (ActionJackson.CanPlayerMoveDown() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X && CBlock::BlockList[i]->Y == ActionJackson.Y + 32) {
                        CBlock::BlockList[i]->OnMove(0, 32);
                    }
                }
                ActionJackson.OnMove(0, 32);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveDown() == YES) {
                ActionJackson.OnMove(0, 32);
                Moves++;
            }
            break;

        case SDLK_LEFT:
            if (ActionJackson.CanPlayerMoveLeft() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X - 32 && CBlock::BlockList[i]->Y == ActionJackson.Y) {
                        CBlock::BlockList[i]->OnMove(-32, 0);
                    }
                }
                ActionJackson.OnMove(-32, 0);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveLeft() == YES) {
                ActionJackson.OnMove(-32, 0);
                Moves++;
            }
            break;

        case SDLK_RIGHT:
            if (ActionJackson.CanPlayerMoveRight() == BLOCK) {
                for (int i = 0; i < CBlock::BlockList.size(); i++) {
                    if (CBlock::BlockList[i]->X == ActionJackson.X + 32 && CBlock::BlockList[i]->Y == ActionJackson.Y) {
                        CBlock::BlockList[i]->OnMove(32, 0);
                    }
                }
                ActionJackson.OnMove(32, 0);
                Moves++;
            }
            if (ActionJackson.CanPlayerMoveRight() == YES) {
                ActionJackson.OnMove(32, 0);
                Moves++;
            }
            break;

        case SDLK_ESCAPE:
            Running = false;
            break;

        default: break;
    }
}
WOW. Let's go through that bunch of code. The same switch statement remains as before, but the inside is different. Let's take just the case of SDLK_UP. If the UP key is pressed: First, the function checks if the player can move, and that is done by calling the CanPlayerMove...() function and checking its return value. If there is a block (i.e. if CanPlayerMove...() returns BLOCK), then a for loop is run. For every block in CBlock's static BlockList vector, there's an if statement that checks if that block is the block that is next to the player. Otherwise, this function could just move any random block up, and that would obviously not be good. So this just figures out which block is next to the player. Once that's figured out, that block moves up, and the for loop continues. It's not going to return to the same block again, so it just keeps running until it's out of options. Then, ActionJackson moves up, sort of "following" the block. And the Move count increments up by one.

If ActionJackson.CanPlayerMove...() doesn't return BLOCK, none of that happens. The next if statement checks if that function returned YES. In this case, ActionJackson very simply moves up, not disturbing anything else, and the Move count increments up by one. Finally, if CanPlayerMove...() returns NO, nothing happens, so when up is pressed and the player can't move up, he DOESN'T.

This repeats for every arrow key. There we go! The game works! Now let's just make a use for CBlock's boolean attribute, OnHomeTile. Go to CApp::OnLoop(), and rewrite it as this:

void CApp::OnLoop() {
    for (int i = 0; i < CEntity::EntityList.size(); i++) {
        if (!CEntity::EntityList[i]) continue;
        CEntity::EntityList[i]->OnLoop();
    }

    bool BlocksAreHome = true;

    for (int i = 0; i < CBlock::BlockList.size(); i++) {
        CBlock::BlockList[i]->CheckForHome();
        if (BlocksAreHome && !CBlock::BlockList[i]->OnHomeTile) {
            BlocksAreHome = false;
        } else { continue; }
    }

    if (BlocksAreHome) {
        Running = false;
    }
}
After the existing first for loop, a new boolean value is created. By default, BlocksAreHome is set to true, but that changes immediately if it isn't true. The next for loop runs through every block in CBlock's BlockList vector. Each of the blocks the loop runs through calls its CheckForHome() function, and accordingly sets its OnHomeTile boolean to true/false. Then, the block in the for loop is checked to be on its home tile with the if statement. If it's not on its home tile, BlocksAreHome is set to false. Otherwise, the for loop continues, leaving BlocksAreHome true. Finally, after the for loop, if BlocksAreHome is true, Running is set to false, and the game ends. This is just all I've written for the end of the level; feel free to add your own end-of-game stuff. Now the game is perfect. All you need to do is make the area, map, blocks, and graphics files. Thankfully, they're all packaged below. Also, in Define.h, set TILE_SIZE to 32, MAP_WIDTH to 20, and MAP_HEIGHT to 15.

The directory tree should be as follows (numbered by sublevel):
1. Sokoban (directory)
2. blocks (directory) gfx (directory), maps (directory), tilesets (directory).

In the blocks directory, put the files in the following .zip archive:
MediaFire - blocks.zip

In the gfx directory, put the files in the following .zip archive:
MediaFire - gfx.zip

In the maps directory, put the files in the following .zip archive:
MediaFire - maps.zip

In the tilesets directory, put the files in the following .zip archive:
MediaFire - tilesets.zip

COPYRIGHT:
Copyright 2008 Arseniy Banayev

This may be not be reproduced under any circumstances except for personal, private use. It may not be placed on any web site or otherwise distributed publicly without advance written permission. Use of this guide on any other web site or as a part of any public display is strictly prohibited, and a violation of copyright. This guide is only to be used on SDLTutorials.com, and only by its host, Tim Jones. If found anywhere else, the proper web law enforcement authorities will be notified for violating copyright.

All trademarks and copyrights contained in this document are owned by their respective trademark and copyright holders.

If you have any questions, comments, or misinformation concerning this guide, please leave comments or, in the rarest case, email me at xboteb13@gmail.com. Any electronic mail that I judge to be spam before opening the contents will be deleted automatically. Any malicious software attached to the electronic mail will not only be ignored and pointless, thanks to Ubuntu's security, but will be promptly reported to the appropriate law enforcement authorities and legal partners.

DISCLAIMER:
I will not be held responsible for any damages, reversible or irreversible, to your operating system, personal files, applications, or the physical computer itself, brought upon as a result of compiling and executing the given code or opening the provided files in the above packages.


SDL Scale Surface
The following user tutorial was created by Mod J., for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.
Today we're going to learn something about extending the capabilities of SDL_Surfaces. If we take a look at OpenGL, what happens when we load a Surface and we render it specifying a width and height bigger than the Surface's original width and height? It gets automatically scaled (unless you specify you want to be repeated). In SDL there is no automatic way of scaling surfaces, so we're going to make a (relatively simple) routine that does the trick.

First, the prototype of the function:
SDL_Surface* SDL_ScaleSurface(SDL_Surface* Surface, Uint16 Width, Uint16 Height);
What are these parameters? Well:
Surface: The original surface the caller of the function wants to be scaled.
Width: The width the caller wants the scaled surface to be (we use Uint16 because this is SDL's standard for width).
Height: The height the caller wants the scaled surface to be (we use Uint16 because this is SDL's standard for height).

Got that? If you didn't, try reading it over again; if you did, then let's move on.

First, we have to check if the Surface's width and height are valid; this is simple to accomplish:
SDL_Surface* SDL_ScaleSurface(SDL_Surface* Surface, Uint16 Width, Uint16 Height)
{
    if(!Surface || !Width || !Height)
        return 0;
}
We don't want a null pointer specified, since it will make BAD things happen when we try to read data from the surface. Secondly, a surface with 0 width or 0 height wouldn't be a good idea, so let's just return a null pointer when either of these things happen.

Now we need to create a surface that suits our needs; we'll do this using the SDL_CreateRGBSurface function:
SDL_Surface* SDL_ScaleSurface(SDL_Surface* Surface, Uint16 Width, Uint16 Height)
{
    SDL_Surface *_ret = SDL_CreateRGBSurface(Surface->flags, Width, Height, Surface->format->BitsPerPixel,
        Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask, Surface->format->Amask);
}
Here we're declaring and defining the return surface. We want its specifications to be the same as the surface we got from the caller, so we do so, and then the width and the height should be the same as the ones the caller specified. Looks pretty simple to me ;-).

Now comes the math part. We need to calculate the scaling (or stretching) factor to be applied when we copy the surface's pixels. It's not as hard as it seems, we just divide the width the caller wants by the width of the surface, what have we calculated then? If you calculate the scaling factor for the widths, you'll get the number of pixels you need for each individual source pixel, in other words, each pixel you read from the source surface has to be drawn SCALING_FACTOR_X times over the width in order to stretch it (the same goes for the height).

Calculations:
SDL_Surface* SDL_ScaleSurface(SDL_Surface* Surface, Uint16 Width, Uint16 Height)
{
    double    _stretch_factor_x = (static_cast<double>(Width)  / static_cast<double>(Surface->w)),
        _stretch_factor_y = (static_cast<double>(Height) / static_cast<double>(Surface->h));
}
Casting might be bad to some of you, but it's necessary here since we need the scaling factors as a double. This probably could have been done better, like checking for w = 0 and h = 0, but this is just a simple tutorial ;-). We're just doing here what I explained before.

Now we need to do the actual drawing. This might look overwhelming, but it's not as hard as it seems. We're just going to make 2 for-loops to run across all the pixels on the source surface, and then 2 more loops for drawing STRETCHING_FACTOR pixels for each pixel that gets read from the source surface.

Here we go!
SDL_Surface* SDL_ScaleSurface(SDL_Surface *Surface, Uint16 Width, Uint16 Height)
{
    for(Sint32 y = 0; y < Surface->h; y++) //Run across all Y pixels.
        for(Sint32 x = 0; x < Surface->w; x++) //Run across all X pixels.
            for(Sint32 o_y = 0; o_y < _stretch_factor_y; ++o_y) //Draw _stretch_factor_y pixels for each Y pixel.
                for(Sint32 o_x = 0; o_x < _stretch_factor_x; ++o_x) //Draw _stretch_factor_x pixels for each X pixel.
                    DrawPixel(_ret, static_cast<Sint32>(_stretch_factor_x * x) + o_x,
                        static_cast<Sint32>(_stretch_factor_y * y) + o_y, ReadPixel(Surface, x, y));
}
I'm not going to specify the DrawPixel and ReadPixel functions here, as they can be gotten from the SDL documentation wiki (http://www.libsdl.org/cgi/docwiki.cgi/Pixel_Access), though my prototype might be a little different, they work like this:
void DrawPixel(SDL_Surface* Target, Sint16 X, Sint16 Y, Uint16 Color); //Draws the specified color at the specified X and Y on the target surface.
Uint32 ReadPixel(SDL_Surface* Source, Sint16 X, Sint16 Y); //Returns the color of the specified surface on the specified X and Y coordinates.
The principle is pretty easy:
for(each_source_pixel) -> draw(scaling_factor_amount_of_pixels_on_the_return_value)
The DrawPixel call might look weird to you, but actually what happens inside is this: Draw a pixel on(the return value, stretching_factor_x * source_x because the destination moves each time and then blit the amount of pixels we need, same for y, what color do we need? The color that's on the source surface at the x and y position, because we need to blit it scaling_factor amount of times).

Why the strange X and Y coordinates? Well, imagine we need to make the width twice as bug, look at this crappy drawing:
Source surface:
---------------------------------------------
|x,y       |0,0       |1,0       |2,0       |
---------------------------------------------

Destination:
---------------------------------------------
|x,y       |0,0 & 1,0 |2,0 & 3,0 |4,0 & 5,0 |
---------------------------------------------
See? When at pixel (0,0) we need to draw a pixel at (0,0) and (1,0) because we need 2 pixels on the destination for each 1 pixel on the source (following the X-axis because it's the width), the same goes for the Y. We're actually just skipping what we've already drawn, _stretch_factor_x * x is actually 'Move to the corresponding pixel on the destination. It makes the (2,0) on the drawing say that it should begin its drawing at (4,0) on the destination, then it loops o_x untill it's smaller than 2, drawing first at (4,0) and then when o_x increments at (5,0). It's THAT easy.

Finally we return the desired surface:
SDL_Surface *SDL_ScaleSurface(SDL_Surface *Surface, Uint16 Width, Uint16 Height)
{
    return _ret;
}
Here an overview of the full function:
SDL_Surface *ScaleSurface(SDL_Surface *Surface, Uint16 Width, Uint16 Height)
{
    if(!Surface || !Width || !Height)
        return 0;

    SDL_Surface *_ret = SDL_CreateRGBSurface(Surface->flags, Width, Height, Surface->format->BitsPerPixel,
        Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask, Surface->format->Amask);

    double    _stretch_factor_x = (static_cast<double>(Width)  / static_cast<double>(Surface->w)),
        _stretch_factor_y = (static_cast<double>(Height) / static_cast<double>(Surface->h));

    for(Sint32 y = 0; y < Surface->h; y++)
        for(Sint32 x = 0; x < Surface->w; x++)
            for(Sint32 o_y = 0; o_y < _stretch_factor_y; ++o_y)
                for(Sint32 o_x = 0; o_x < _stretch_factor_x; ++o_x)
                    DrawPixel(_ret, static_cast<Sint32>(_stretch_factor_x * x) + o_x,
                        static_cast<Sint32>(_stretch_factor_y * y) + o_y, ReadPixel(Surface, x, y));

    return _ret;
}
I hope you learned something with this tutorial, maybe I'll write more tutorials in the future (perhaps something on texture repeating? ;-)).

See ya!
Creature


SDL Net - Part 1

User Tutorial
The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.

In this tutorial, I will show you a simple c++ library to add network resources to your game.

Firstly, we have to add the SDL_net headers and libraries to your desktop environment. If you are using a linux distro like Ubuntu, it is enough to download the libsdl-netx.x-dev package from the repositories. If not, the process is analogous of including sdl mixer, image, etc, better explained by Tim's Tutorials. Don't forget, as well, to set the linker options in your IDE, adding the SDL_net library.

So, let's create our two files, CNet.h and CNet.cpp, that will build our framework that we'll help us at making network games. It's necessary to say that this library, for the time being, only works with TCP/IP protocol.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cstring>
#include "SDL_net.h"

typedef char charbuf [256];

class CNet{
  public:
    static bool Init(); //Initialize SDL_net
    static void Quit(); //Exit SDL_net
};
Here is our header file, and, after the includes and the charbuf declaration, our first class. Its aim is initialize and end the network environment, with two static functions. However, they are unnecessary, if we're using SDL_Init(SDL_INIT_EVERYTHING).
class CNetMessage {
  protected:
    charbuf buffer; //message stored in buffer, limited to 256 bytes
    enum bufstates { //message states
    EMPTY,
    READING,
    WRITING,
    FULL };
    bufstates state; //the message state
    void reset(); // reset message: fulfill it with zeroes and change its state to EMPTY
  public:
    CNetMessage(); //constructor
//Virtual member function that indicates how many bytes may have to be loaded into the instance. By default, it works with strings that have a maximum
//of 255 characters.
    virtual int NumToLoad();

//Virtual member function that indicates how many bytes may have to be downloaded from the instance.
    virtual int NumToUnLoad();

    void LoadBytes(charbuf& inputbuffer, int n); //Load a char set into the message buffer
    void UnLoadBytes(charbuf& destbuffer); //Unload a char set from the buffer
    void finish(); //set the state object to full. No more data to be loaded
};
The class above provides the needed interface for the application to deal with the packets sent through the network. It's limited to a maximum of 256 bytes.

Regardless this class uses a buffer with a fixed length, the packets sent through the network can be much more smaller, according to the program's needs. In our TIC TAC TOE game, only one byte is sent. To work with this customization, we put some message states, to help the framework user to build a derived class.

The LoadBytes and UnloadBytes member functions load and unload in a buffer the wanted chars. They were determined by the virtual functions NumToLoad and NumToUnload, each of which indicates how many bytes have to be loaded or unloaded. By default, this class is able to work with strings that have a maximum of 255 characters. If you want to work with other data structures, as we'll do at the game making, you may override this member function.

Last, the finish() member function, which sets the class ready to be used. So, we can send through the socket, in the same packet, a message with two different data structures, avoiding unnecessary network pollution. Below, the implementation code of this and the latter classes:
#include "CNet.h"

bool CNet::Init() {
    if (SDLNet_Init() < 0) {
        std::cerr << "SDLNet_Init: " << SDLNet_GetError() << std::endl;
          return false;
        }
        else
            return true;
}

void CNet::Quit() {
    SDLNet_Quit();
}

CNetMessage::CNetMessage() {
    reset();
}

void CNetMessage::reset() {
    for (int i =0; i < 256; i++)
        buffer [i] = 0;
    state = EMPTY;
}

void CNetMessage::finish() {
    if (state == READING)
        state = FULL;
}

int CNetMessage::NumToLoad() {
    if (state == EMPTY)
        return 255;
    else
        return 0;
}

int CNetMessage::NumToUnLoad() {
    if (state == FULL)
        return strlen(buffer) + 1;
    else
        return 0;
}

void CNetMessage::LoadBytes(charbuf& inputbuffer, int n) {
    for (int i = 0; i < n; i++)
        buffer[i] = inputbuffer[i];
    state = READING;
}

void CNetMessage::UnLoadBytes(charbuf& destbuffer) {
    for (int i=0; i < this->NumToUnLoad(); i++)
        destbuffer[i] = buffer[i];
    reset();
}
Now, back to the header file. Let's take a look at the CIpAddress class:
class CIpAddress {
  private:
    IPaddress m_Ip; //the IPaddress structure
  public:
    CIpAddress(); //constructor
    CIpAddress (Uint16 port); //create and associate a port to the instance
    CIpAddress (std::string host, Uint16 port); //create and associate a port and a host to the instance
    void SetIp (IPaddress sdl_ip); //set a CIpAddress object from an existing SDL IPaddress
    bool Ok() const; //True if the object have a port and a host associated to it
    IPaddress GetIpAddress() const; //return a SDL_net IPaddress structure
    Uint32 GetHost() const; //return the host
    Uint16 GetPort() const; //return the port
};
Basically, that is a C++ interface for the SDL NET structure IPaddress. It has informations about the host IP address, and the port which the program is associated to. Here is the definition of the class members, at CNet.cpp:
CIpAddress::CIpAddress (Uint16 port) {
    if (SDLNet_ResolveHost(&m_Ip, NULL, port) < 0){
        std::cerr << "SDLNet_ResolveHost: " << SDLNet_GetError() << std::endl;
        m_Ip.host = 0;
        m_Ip.port = 0;
    }
}

CIpAddress::CIpAddress (std::string host, Uint16 port) {
    if (SDLNet_ResolveHost(&m_Ip, host.c_str(), port) < 0) {
        std::cerr << "SDLNet_ResolveHost: " << SDLNet_GetError() << std::endl;
        m_Ip.host = 0;
        m_Ip.port = 0;
    }
}

CIpAddress::CIpAddress() {
    m_Ip.host = 0;
    m_Ip.port = 0;
}

bool CIpAddress::Ok() const {
    return !(m_Ip.port == 0);
}

void CIpAddress::SetIp (IPaddress sdl_ip) { //sets a CTcpSocket object from a existing SDL socket
    m_Ip = sdl_ip;
}

IPaddress CIpAddress::GetIpAddress() const {
    return m_Ip;
}

Uint32 CIpAddress::GetHost() const {
    return m_Ip.host;
}

Uint16 CIpAddress::GetPort() const {
    return m_Ip.port;
}
Again, back to header, let's analyze CTcpSocket, a framework to the SDL NET TCP socket structure. In a few words, a socket is a connection, a "pipe" between two computers, that delivers messages to them, in and out. In a TCP socket, there are necessarily two fixed computers in each pipe's end, and messages always are got in the right order that they have sent.
class CTcpSocket {
  protected:
    TCPsocket m_Socket; //the TCP socket structure
    SDLNet_SocketSet set; //a set of sockets. Used here only to check existing packets
  public:
    CTcpSocket();
    virtual ~CTcpSocket();
    virtual void SetSocket (TCPsocket  the_sdl_socket); //set a CTcpSocket object from a existing SDL socket
    bool Ok() const; //indicate if theres is a TCPsocket associated to the instance
    bool Ready() const; //True if there are bytes ready to be read
    virtual void OnReady(); //pure virtual
};
In the code above, the member function Ready() plays an important role: it checks whether there is or not activity in the socket, freeing the program's flow to do what they have to do, without waiting messages from the other side of the pipe. Below, the code in CNet.cpp:
CTcpSocket::CTcpSocket() {
    m_Socket = NULL;
    set = SDLNet_AllocSocketSet(1);
}

CTcpSocket::~CTcpSocket() {
    if (!(m_Socket == NULL)) {
          SDLNet_TCP_DelSocket(set,m_Socket);
        SDLNet_FreeSocketSet(set);
        SDLNet_TCP_Close (m_Socket);
    }
}

void CTcpSocket::SetSocket (TCPsocket the_sdl_socket) {
    if (!(m_Socket == NULL)) {
          SDLNet_TCP_DelSocket(set,m_Socket);
          SDLNet_TCP_Close (m_Socket);
      }
    m_Socket = the_sdl_socket;
    if(SDLNet_TCP_AddSocket(set,m_Socket)==-1) {
        std::cerr << "SDLNet_TCP_AddSocket: " << SDLNet_GetError() << std::endl;
        m_Socket = NULL;
    }
}

bool CTcpSocket::Ok() const {
    return !(m_Socket == NULL);
}

bool CTcpSocket::Ready() const {
    bool rd = false;
    int numready = SDLNet_CheckSockets(set, 0);
    if (numready == -1)
            std::cerr << "SDLNet_CheckSockets: " << SDLNet_GetError() << std:: endl;
      else
            if (numready)
                rd = SDLNet_SocketReady (m_Socket);
      return rd;
}

void CTcpSocket::OnReady() {

}
From this class, we will derive CHostSocket, according to the piece of code of CNet.h:
class CClientSocket;

class CHostSocket : public CTcpSocket {
  public:
    CHostSocket (CIpAddress& the_ip_address); //create and open a new socket, with an existing CIpAddress object

    CHostSocket (Uint16 port); //create and open a new socket with the desired port
    bool Accept (CClientSocket&); //set a client CTcpSocket object after listening to the port
    virtual void OnReady(); //pure virtual
};
A host socket is a socket whose utility is listen to a certain port, to check whether there is someone wanting to connect to the service provided by the program, in the machine it runs. If found, we have to use the member Accept to associate another socket to the client. Here is the implementation:
CHostSocket::CHostSocket (CIpAddress& the_ip_address) {
    CTcpSocket::CTcpSocket();
    IPaddress iph = the_ip_address.GetIpAddress();
    if (!(m_Socket = SDLNet_TCP_Open(&iph))) {
            SDLNet_FreeSocketSet(set);
            std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
    }
}

CHostSocket::CHostSocket (Uint16 port) {
    CIpAddress iplistener (port);
    if (!iplistener.Ok()) {
        m_Socket = NULL;
    }
    else {
        CTcpSocket::CTcpSocket();
        IPaddress iph = iplistener.GetIpAddress();
        if (!(m_Socket = SDLNet_TCP_Open(&iph))) {
            SDLNet_FreeSocketSet(set);
            std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
        }
    }
}

bool CHostSocket::Accept (CClientSocket& the_client_socket) {
    TCPsocket cs;
    if ((cs = SDLNet_TCP_Accept(m_Socket))) {
        the_client_socket.SetSocket(cs);
        return true;
    }
    else
        return false;
}

void CHostSocket::OnReady() {

}
So, a tcp socket client is a socket whose aim is to communicate with a remote host, who, in turn, is using a tcp client to send and receive messages too. Below, the header file for this class:
class CClientSocket : public CTcpSocket {
  private:
    CIpAddress m_RemoteIp; //the CIpAddress object corresponding to the remote host
  public:
    CClientSocket(); //constructor
    CClientSocket (std::string host, Uint16 port); //Create the object and connect to a host, in a given port
    bool Connect (CIpAddress& remoteip); //make a connection to communicate with a remote host
    bool Connect (CHostSocket& the_listener_socket); //make a connection to communicate with a client
    void SetSocket (TCPsocket  the_sdl_socket); //set a CTcpSocket object from an existing SDL_net socket
    CIpAddress getIpAddress () const; //return a CIpAddress object associated to the remote host
    virtual void OnReady(); //pure virtual
    bool Receive(CNetMessage& rData); //receive data and load it into a CNetMessage object
    bool Send (CNetMessage& sData); //send data from a CNetMessage object
};
The same way CHostSocket is, this class is derived from CTcpSocket, and uses the constructor or Connect() to make a connection to the host. If so, the object uses the Send() and Receive() member functions to throw and get messages through the network. Below, the implementation:
CClientSocket::CClientSocket() {
    CTcpSocket::CTcpSocket();
}

CClientSocket::CClientSocket (std::string host, Uint16 port) {
    CIpAddress remoteip (host.c_str(), port);
    if (!remoteip.Ok()) {
        m_Socket = NULL;
    }
    else {
        CTcpSocket::CTcpSocket();
        Connect(remoteip);
    }
}

bool CClientSocket::Connect (CHostSocket& the_listener_socket) {
    return false;
}

bool CClientSocket::Connect (CIpAddress& remoteip) {
    TCPsocket cs;
    IPaddress ip = remoteip.GetIpAddress();
    if ((cs = SDLNet_TCP_Open(&ip)))
    {
        SetSocket(cs);
        return true;
    }
    else {
        std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
        return false;
    }
}

void CClientSocket::SetSocket (TCPsocket the_sdl_socket) {
    CTcpSocket::SetSocket (the_sdl_socket);
    IPaddress* ips;
    if ((ips = SDLNet_TCP_GetPeerAddress(m_Socket))) {
    /* Print the address, converting it onto the host format */
        m_RemoteIp.SetIp(*ips);
        Uint32 hbo = m_RemoteIp.GetHost();
        Uint16 pbo = m_RemoteIp.GetPort();
        std::cout << "Client connected: " << SDLNet_Read32(&hbo) << ' '
            << SDLNet_Read16 (&pbo) << std::endl;
    }
    else
        std::cerr << "SDLNet_TCP_GetPeerAddress: " << SDLNet_GetError() << std::endl;
}

CIpAddress CClientSocket::getIpAddress () const {
    return m_RemoteIp;
}

bool CClientSocket::Receive(CNetMessage& rData) {
//Firstly, check if there is a socket
    if (m_Socket == NULL)
        return false;
    charbuf buf;

//Check if the instance can receive bytes, if it can, load the number of bytes specified by NumToLoad() virtual function
    while (rData.NumToLoad() > 0) {
        if (SDLNet_TCP_Recv(m_Socket, buf, rData.NumToLoad()) > 0) {
            rData.LoadBytes (buf, rData.NumToLoad());
        }
        else {
            return false;
        }
    }
    rData.finish();
    return true;
}

bool CClientSocket::Send (CNetMessage& sData) {
//check if there is a socket
    if (m_Socket == NULL)
        return false;
    charbuf buf;
    int len;

//Check if the instance can send bytes, if it can, unload the number of bytes specified by NumToLoad() virtual function
    while ((len = sData.NumToUnLoad()) > 0) {
        sData.UnLoadBytes (buf);
        if (SDLNet_TCP_Send(m_Socket, (void *)buf, len) < len) {
            std::cerr << "SDLNet_TCP_Send: " << SDLNet_GetError() << std::endl;
            return false;
        }
    }
    return true;
}

void CClientSocket::OnReady() {

}


SDL Net - Part 2

User Tutorial
The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.

Once we have made our common library, it's time to think on designing the game. Network games can have many architectures, but, normally, there are always at least two applications: a server and a client.

A server can be dedicated, like most online multiplayer games over the internet. But, in this case, the server will be playing too. To make things simpler, the server always will be "X", and the player that starts the game, too. It'll be responsible for waiting clients to connect, too.

At each player turn, the program sets the grid status, refreshes the screen, and sends a message to the other player. Receiving this message, the other player is now able to make valid inputs, as the first one can only exit or minimize his application.

At this point, we may have to create a state for the application: connected. Without it, one can start the game alone, and send messages to a person who is not online.

Now, let's take a look at the messages the program has to make. As we said in the first tutorial, we will send only a byte, that corresponds to the ID of the grid cell clicked. So we have to modify our CNetMessage class, as this byte can be zero, and the program can take the message as an empty string. Therefore, we will make the class CNetMessageApp. Here is the header file, CNetApp.h:
#include "CNet.h"

class CNetMessageApp : public CNetMessage {

 private:

//Virtual function that indicates how many bytes may have to be loaded onto the object. Overrides the parent class function member to work with only a byte

   virtual int NumToLoad();

//Virtual function that indicates how many bytes may have to be downloaded from the object. Overrides the parent class function member to work with only a byte

   virtual int NumToUnLoad();

public:

//Function simpler than LoadBytes(), inherited from the parent class, as it only works with one byte

   void LoadByte(char);

//Function simpler than UnLoadBytes(), inherited from the parent class, as it only works with one byte

   char UnLoadByte();

};
Here comes the implementation, at CNetApp.cpp:
#include "CNetApp.h"

int CNetMessageApp::NumToLoad(){

if (state == EMPTY)

 return 1;

else

 return 0;

}

int CNetMessageApp::NumToUnLoad() {

if (state == FULL)

 return 1;

else

 return 0;

}

void CNetMessageApp::LoadByte(char ID) {

   charbuf c;

   c[0] = ID;

LoadBytes(c, 1);

finish();

}

char CNetMessageApp::UnLoadByte() {

   charbuf c;

   UnLoadBytes (c);

   return c[0];

}
As we can see, very simple. Finally, we can modify the original files of Tim's TIC TAC TOE game.

At the header file CApp.h, in the client side, we will have to add the following lines:
//adding the network library

#include "CNetApp.h"

 .

 .

 .

   private:

// Network status indicator

       bool Connected;

// Network objects

       CClientSocket* tcpclient;

       CIpAddress* remoteip;

       CNetMessageApp msg;
And, in the server side, we don't need the help of CIpAddress object, so it can be deleted. However, we have to add a tcp socket that looks for - or, better saying, listen to - clients:
//network objects

       CHostSocket* tcplistener;
And we are able to get into the implementation code. Firstly, let's set this members when creating Capp object. At CApp.cpp, in the server side, we'll have to add these lines to the constructor:
   tcplistener = NULL;

   tcpclient = NULL;

   Connected = false;
And, in the client side,
   Connected = false;

   tcpclient = NULL;

   remoteip = NULL;
Now, let's see CApp_Oninit.cpp. We may have only to create the objects above, except CNetMessage, that is created automatically by CApp. In the server side, the code of CApp::OnInit() will be added by these lines:
//Creating host socket

   tcplistener = new CHostSocket (1234);

   if (!tcplistener->Ok())

       exit(EXIT_FAILURE);

//Creating client socket

   tcpclient = new CClientSocket();
And in the client side,
//Creating Ip and Socket objects

   tcpclient = new CClientSocket ();

   remoteip = new CIpAddress ("192.168.0.102", 1234);
Note that the client, in this example, will communicate with a determined machine. In my home network, "192.168.0.102" will be the server's address, and "1234" will be the port we have chosen to use. Otherwise, in a LAN, there is a manner to broadcast a required connection, but this method uses UDP sockets. Thus, someone can use this kind of protocol to get the server address, and, after that, work with TCP sockets to make reliable and simple communication over the network.

On the other hand, that is the code on CApp_OnCleanup - it will destroy these objects:

Server side:
   delete tcplistener;

   delete tcpclient;
Client side:
   delete remoteip;

   delete tcpclient;
Now, we can examine what happens when we click the mouse in our application. At CApp::OnLButtonDown() we'll change these lines:
if(CurrentPlayer == 0) {
 SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
 }else{
  SetCell(ID, GRID_TYPE_O);
 CurrentPlayer = 0;
 }
}
to these: CApp_OnEvent - Server side:
//Only starts to play once connected

   if (Connected) {

//Player == 0 will always be Server. Always starts to play and always be "X"

       if(CurrentPlayer == 0) {

           SetCell(ID, GRID_TYPE_X);

           CurrentPlayer = 1;

//Send a message to the client, telling the ID of the clicked cell

           msg.LoadByte((char) ID);

           tcpclient->Send(msg);

       }

   }
CApp_OnEvent - Client side:
//Player == 0 will always be Server. Always starts to play and always be "X". So if

//CurrentPlayer is not zero, the game is necessarily connected

if(CurrentPlayer != 0) {

 SetCell(ID, GRID_TYPE_O);

 CurrentPlayer = 0;

//Send a message to the server, telling the ID of the clicked cell

 msg.LoadByte((char) ID);

  tcpclient->Send(msg);

}
As we can see, no much changes have been made. If it's not the application's turn, it does nothing. If it is, the application sets the grid and player status, like it did priorly, and sends a message to the remote application. There's no need to modify the rendering behavior.

Lastly, we have to write CApp OnLoop() member function. We have to do two things: try to connect, or, if yet connected, receive the messages sent over the network. Below, the code of CApp_OnLoop.cpp, server side:
#include "CApp.h"

void CApp::OnLoop() {

//if not connected, listen to the port to detect if there is a client waiting there

   if (!Connected) {

       if (tcplistener->Accept (*tcpclient)) {

           Connected = true;

       }

   }

   else {

//if connected, checks the socket for messages ready to be read

       if (tcpclient->Ready()) {

// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends

// a message with no bytes

           if (tcpclient->Receive (msg)) {

               //if there is a valid message, we can set the grid and player status

               SetCell((int) msg.UnLoadByte(), GRID_TYPE_O);

               CurrentPlayer = 0;

           }

           else

               Connected = false;

       }

   }

}
The commented lines say by themselves the program's flow. On the other side, the client's code:
//if not connected, try to connect to the server

   if (!Connected) {

       if (tcpclient->Connect(*remoteip)) {

           if (tcpclient->Ok()) {

               Connected = true;

           }

       }

}

   else {

//if connected, checks the socket for messages ready to be read

       if (tcpclient->Ready()){

// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends

// a message with no bytes

           if (tcpclient->Receive (msg)) {

               //if there is a valid message, we can set the grid and player status

               SetCell((int) msg.UnLoadByte(), GRID_TYPE_X);

               CurrentPlayer = 1;

           }

           else {

               Connected = false;

           }

       }

   }

}


SDL Per Pixel Collision
Are bounding box and circle collisions just not cutting it? Well, now we're going to dive right down into pixel precision!

Before we start, I'd like to say that this is an advanced SDL tutorial. You should have experience with creating SDL applications and some basic knowledge of getting a program with it's libraries up and running.

First, we'll start off with a very simple Game class to build the foundation of our demo project.

Game.h
#ifndef GAME_H
#define GAME_H

#include "SDL.h"

class Game
{
    public:
        Game();
        ~Game();

        void OnExecute();

        void OnThink();
        void OnUpdate();
        void OnRender();

    private:
        int ScreenWidth;
        int ScreenHeight;
        int ScreenBPP;
        Uint32 ScreenFlags;

        bool done;

        double thisTime;
        double lastTime;
        double deltaTime;

        SDL_Surface* screen;
        SDL_Event event;
};

#endif
Game.cpp
#include "Game.h"

Game::Game()
{
    ScreenWidth = 800;
    ScreenHeight = 600;
    ScreenBPP = 32;
     ScreenFlags = SDL_HWSURFACE | SDL_DOUBLEBUF;

    thisTime = 0.0;
    lastTime = 0.0;
    deltaTime = 0.0;

    done = false;

    SDL_Init(SDL_INIT_EVERYTHING);

    SDL_WM_SetCaption("SDL Per Pixel Collision", NULL);
    SDL_ShowCursor(0);

    screen = SDL_SetVideoMode(ScreenWidth, ScreenHeight, ScreenBPP, ScreenFlags);
}

Game::~Game()
{
    SDL_Quit();
}

void Game::OnExecute()
{
    while(!done)
    {
        OnThink();
        OnUpdate();
        OnRender();
    }
}

void Game::OnThink()
{
    while(SDL_PollEvent(&event))
    {
        if(event.type == SDL_QUIT)
            done = true;

        if(event.type == SDL_KEYDOWN)
            if(event.key.keysym.sym == SDLK_ESCAPE)
                done = true;
    }
}

void Game::OnUpdate()
{
    lastTime = thisTime;
    thisTime = SDL_GetTicks();
    deltaTime = (thisTime - lastTime) / 1000.0;
}

void Game::OnRender()
{
    SDL_FillRect(screen, NULL, 0);

    SDL_Flip(screen);
}

int main(int argc, char* args[])
{
    Game game;
    game.OnExecute();

    return 0;
}
You should be able to compile and run at this point. Now we have a nice 800x600 window prepared for our bidding! The three double variables are used at keep track of elapsed time each tick. You can simply substitute in your favorite Timer class if you wish. I also turn off cursor visibility since we'll be controlling an object with the mouse and we don't want it to obscure our view.

Now we'll need some sort of objects that we can collide together!

Entity.h
#ifndef ENTITY_H
#define ENTITY_H
#include "SDL.h"
#include "SDL_image.h"

struct Vector
{
    float X;
    float Y;

    Vector()
    {
        X = 0.0f;
        Y = 0.0f;
    }
};

class Entity
{
    public:
        Entity(char* filename, int tilesX, int tilesY, double frameRate);
        ~Entity();

    public:
        Vector position;
        Vector origin;

    public:
        SDL_Surface* surface;

        int tilesX;
        int tilesY;
        int tileWidth;
        int tileHeight;

        int frame;
        int frameCount;

        double frameInterval;
        double frameTimeRemaining;

    public:
        void Update(double deltaTime);
        void Render(SDL_Surface* screen);
};

#endif
Simple enough, I hope. For each Entity we create, we'll take in a filename for the for the image we'll be loading. If you didn't notice, SDL_image is included, as I'll be working with PNG files with alpha transparency. Next, we take in the tiles across and tiles down in the spritesheet we'll be using. Finally, a frame rate to determine how fast the sprite will animate.

Next, the member variables. I've created a simple Vector struct to hold position and origin information. Position is where the Entity will sit on the screen and origin is the center of the tile currently being drawn. This will make things simpler when we want to draw a sprite centered on it's position. Of course, an SDL_Surface pointer to hold the image data we'll be using. And last, variables to hold information about the spritesheet we'll be using and handling animation.

Finally, two methods to update and render the given Entity. Now let's implement these!

Entity.cpp
#include "Entity.h"

Entity::Entity(char* filename, int tilesX, int tilesY, double frameRate)
{
    this->surface = IMG_Load(filename);

    this->tilesX = tilesX;
    this->tilesY = tilesY;
    this->tileWidth = surface->w / tilesX;
    this->tileHeight = surface->h / tilesY;
    this->frame = 0;
    this->frameCount = tilesX * tilesY;
    this->frameInterval = 1 / frameRate;
    this->frameTimeRemaining = frameInterval;

    this->origin.X = tileWidth / 2.0f;
    this->origin.Y = tileHeight / 2.0f;
}

Entity::~Entity()
{
    SDL_FreeSurface(surface);
}

void Entity::Update(double deltaTime)
{
    frameTimeRemaining -= deltaTime;
    if(frameTimeRemaining < 0)
    {
        frame++;
        frame %= frameCount;
        frameTimeRemaining = frameInterval;
    }
}

void Entity::Render(SDL_Surface* screen)
{
    SDL_Rect location = GetBounds();
    SDL_Rect clip = GetFrameBounds();

    SDL_BlitSurface(surface, &clip, screen, &location);
}
The constructor loads in our image using IMG_Load() and stores all our data about the sprite sheet. The destructor ensures we have no memory leaks by freeing our SDL_Surface. Update chips away at our frameTimeRemaining with a given deltaTime. Once we hit or are under zero, we increase the frame, wrap around to the beginning if need be, and reset our frameTimeRemaining with our given frameInterval. Render finds our location and clip rectangles and then blits the surface to the screen. But wait, we haven't added GetBounds or GetFrameBounds() yet. I'm sure those will be useful to have! Let's get to that!

Add these method definitions in:

Entity.h
    public:
        SDL_Rect GetBounds();
        SDL_Rect GetFrameBounds();
And now we'll implement them:

Entity.cpp
SDL_Rect Entity::GetBounds()
{
    SDL_Rect bounds;
    bounds.x = (Sint16)(position.X - origin.X);
    bounds.y = (Sint16)(position.Y - origin.Y);
    bounds.w = (Sint16)(tileWidth);
    bounds.h = (Sint16)(tileHeight);

    return bounds;
}

SDL_Rect Entity::GetFrameBounds()
{
    SDL_Rect frameBounds;
    frameBounds.x = (Sint16)(frame % tilesX * tileWidth);
    frameBounds.y = (Sint16)(frame / tilesX * tileHeight);
    frameBounds.w = (Sint16)(tileWidth);
    frameBounds.h = (Sint16)(tileHeight);

    return frameBounds;
}
GetBounds() takes our position and shifts it over by our origin. This places the position at the center of the sprite, rather than the top left. GetFramesBound() checks what frame we're on and finds the matching coordinates. Very important for animation!

Now lets slap a couple of these suckers into our demo!

Game.h
#include "Entity.h"

class Game
{
    ...

    private:
        Entity* ship;
        Entity* asteroid;
};
Make sure you include Entity.h so we can actually use the class and we'll add two Entity pointers to our Game class.

Game.cpp
Game::Game()
{
    ...

    ship = new Entity("images/ship.png", 4, 3, 10);
    asteroid = new Entity("images/asteroid.png", 5, 6, 30);

    asteroid->position.X = (float)(ScreenWidth / 2);
    asteroid->position.Y = (float)(ScreenHeight / 2);
}

Game::~Game()
{
    delete ship;
    delete asteroid;

    ...
}
Image: Ship
Image: Asteroid
(Special thanks to 3DBuzz for these images.)

We'll now dynamically allocate memory for our two new Entity objects. I have my images sitting in an "images" folder, but you may do whatever you want! I then position the asteroid at the center of the screen. And because we're mindful programmers, we'll delete the allocated memory once we're all done.

Game.cpp
void Game::OnThink()
{
    while(SDL_PollEvent(&event))
    {
        if(event.type == SDL_QUIT)
            done = true;

        if(event.type == SDL_KEYDOWN)
            if(event.key.keysym.sym == SDLK_ESCAPE)
                done = true;

        if(event.type == SDL_MOUSEMOTION)
        {
            ship->position.X = event.motion.x;
            ship->position.Y = event.motion.y;
        }
    }
}

void Game::OnUpdate()
{
    lastTime = thisTime;
    thisTime = SDL_GetTicks();
    deltaTime = (thisTime - lastTime) / 1000.0;

    ship->Update(deltaTime);
    asteroid->Update(deltaTime);
}

void Game::OnRender()
{
    SDL_FillRect(screen, NULL, 0);

    ship->Render(screen);
    asteroid->Render(screen);

    SDL_Flip(screen);
}
In OnThink, we'll mouse movement to our ship by looking for an SDL_MOUSEMOTION event while polling for events. In OnUpdate, we'll simply call each Entity's Update method with the deltaTime we found. Lastly, in OnRender, we'll just call the Render method by passing our screen.

Compile, run, and give it a whirl! Weeee! We now have two animated sprites, one centered in the screen and one controlled by our mouse. But where's the collision? Oh yeah, we haven't implemented it yet!

We'll need a bit more functionally in our Entity class, so let's work on that. This one's a doozy, so hold on tight! But don't worry, we'll take it one step at a time...

Entity.h
class Entity
{
    ...

    public:
        SDL_Rect NormalizeBounds(const SDL_Rect& rect);

        static SDL_Rect Intersection(const SDL_Rect& boundsA, const SDL_Rect& boundsB);
        static bool CheckCollision(Entity* entityA, Entity* entityB);
        static bool GetAlphaXY(Entity* entity, int x, int y);
};
Entity.cpp
SDL_Rect Entity::NormalizeBounds(const SDL_Rect& rect)
{
    SDL_Rect normalized;
    normalized.x = rect.x - (Sint16)position.X + (Sint16)origin.X + GetFrameBounds().x;
    normalized.y = rect.y - (Sint16)position.Y + (Sint16)origin.Y + GetFrameBounds().y;
    normalized.w = rect.w;
    normalized.h = rect.h;

    return normalized;
}
Given the SDL_Rect of a collision rectangle, we want to find what pixels on the sprite sheet correlate to it. A little messy, but it gets the job done.

Entity.cpp
SDL_Rect Entity::Intersection(const SDL_Rect& boundsA, const SDL_Rect& boundsB)
{
    int x1 = Maximum(boundsA.x, boundsB.x);
    int y1 = Maximum(boundsA.y, boundsB.y);
    int x2 = Minimum(boundsA.x + boundsA.w, boundsB.x + boundsB.w);
    int y2 = Minimum(boundsA.y + boundsA.h, boundsB.y + boundsB.h);

    int width = x2 - x1;
    int height = y2 - y1;

    if(width > 0 && height > 0)
    {
        SDL_Rect intersect = {x1, y1, width, height};
        return intersect;
    }
    else
    {
        SDL_Rect intersect = {0, 0, 0, 0};
        return intersect;
    }
}
Given the two bounds of two entities, we want to find how much they intersect, if at all. If they do, we're given their intersection rectangle, if not, we're given an empty rectangle. For Max and Min, you can either include "cmath" or use a couple macros such as this:
#define Maximum(a, b) ((a > b) ? a : b)
#define Minimum(a, b) ((a < b) ? a : b)
Entity.cpp
bool Entity::CheckCollision(Entity* entityA, Entity* entityB)
{
    SDL_Rect collisionRect = Intersection(entityA->GetBounds(), entityB->GetBounds());

    if(collisionRect.w == 0 && collisionRect.h == 0)
        return false;

    SDL_Rect normalA = entityA->NormalizeBounds(collisionRect);
    SDL_Rect normalB = entityB->NormalizeBounds(collisionRect);

    for(int y = 0; y < collisionRect.h; y++)
        for(int x = 0; x < collisionRect.w; x++)
            if(GetAlphaXY(entityA, normalA.x + x, normalA.y + y) && GetAlphaXY(entityB, normalB.x + x, normalB.y + y))
                return true;

    return false;
}
This is where bulk of our per pixel collision check happens. First, we ensure there's even a bounding box collision. No use going through all the trouble when we know nothing will happen. Then we normalize the bounds of each entity with the found collision rectangle. This will give us the region of pixels we'll be comparing against each other. You'll notice this GetAlphaXY() function. This is where we'll get down into the pixels and determine if they're solid enough for collision. You'll see that it's called twice in one if-statement. This is because we're searching for a solid enough pixel in each surface at the same position. Once that is found, we'll confirm a hit!

Entity.cpp
bool Entity::GetAlphaXY(Entity* entity, int x, int y)
{
    int bpp = entity->surface->format->BytesPerPixel;
    Uint8* p = (Uint8*)entity->surface->pixels + y * entity->surface->pitch + x * bpp;
    Uint32 pixelColor;

    switch(bpp)
    {
        case(1):
            pixelColor = *p;
            break;
        case(2):
            pixelColor = *(Uint16*)p;
            break;
        case(3):
            if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
                pixelColor = p[0] << 16 | p[1] << 8 | p[2];
            else
                pixelColor = p[0] | p[1] << 8 | p[2] << 16;
            break;
        case(4):
            pixelColor = *(Uint32*)p;
            break;
    }

    Uint8 red, green, blue, alpha;
    SDL_GetRGBA(pixelColor, entity->surface->format, &red, &green, &blue, &alpha);

    return alpha > 200;
}
Oh my, some crazy looking stuff here! It's not that bad. Let me explain a few things here.

We're passing in a pointer to an Entity and an (x,y) coordinate. All we're doing in this function is finding the pixel at that coordinate and determining what colors its composed of. The first two lines give us a pointer to the pixel in question. The funky looking switch-statement is finding the color composition depending on the image's bytes-per-pixel format, either 8-bit, 16-bit, 24-bit, or 32-bit. In our case, it will always land on case(4), but this should give you an idea on how to expand to other formats.

Finally, we're grabbing the RGBA components of the pixel using SDL_GetRGBA. All we care about is alpha, but you can experiment with collision where hits will only happen when a pixel is red or blue enough. Alpha will range between 0 and 255. I wanted collision to happen with not just pixels that were completely opaque, but not too transparent. I found 200 to be a nice number, but you're free to tweak as you please.

Well that's it! Our Entity class has the functionally to test for per pixel collision... so lets show it off!

Game.cpp
void Game::OnRender()
{
    SDL_FillRect(screen, NULL, 0);

    SDL_Rect collisionRect = Entity::Intersection(ship->GetBounds(), asteroid->GetBounds());

    if(collisionRect.w != 0 && collisionRect.h != 0)
        if(Entity::CheckCollision(ship, asteroid))
            SDL_FillRect(screen, &collisionRect, SDL_MapRGB(screen->format, 255, 0, 0));
        else
            SDL_FillRect(screen, &collisionRect, SDL_MapRGB(screen->format, 0, 255, 0));

    ship->Render(screen);
    asteroid->Render(screen);

    SDL_Flip(screen);
}
Adding this checks for collision between our ship and asteroid. When bounding box collision happens, a green box will be drawn behind them. When per pixel collision happens, the box will turn red!

Well, that's about it. Run, compile, and enjoy! You now have per pixel collision checks between your two Entity objects! Feel free to tweak numbers, use different images, or control them in different ways! I've given you the foundation, now go out there and create some interesting implementations!

Here's the final code in case you may have some errors and need to check against my own.

Game.h
#ifndef GAME_H
#define GAME_H
#include "Entity.h"

class Game
{
    public:
        Game();
        ~Game();

        void OnExecute();

        void OnThink();
        void OnUpdate();
        void OnRender();

    private:
        int ScreenWidth;
        int ScreenHeight;
        int ScreenBPP;
        Uint32 ScreenFlags;

        bool done;

        double thisTime;
        double lastTime;
        double deltaTime;

        SDL_Surface* screen;
        SDL_Event event;

        Entity* ship;
        Entity* asteroid;
};

#endif

Game.cpp

#include "Game.h"

Game::Game()
{
    ScreenWidth = 800;
    ScreenHeight = 600;
    ScreenBPP = 32;
    ScreenFlags = SDL_HWSURFACE | SDL_DOUBLEBUF;

    done = false;

    thisTime = 0.0;
    lastTime = 0.0;
    deltaTime = 0.0;

    SDL_Init(SDL_INIT_EVERYTHING);

    SDL_WM_SetCaption("SDL Per Pixel Collision", NULL);
    SDL_ShowCursor(0);

    screen = SDL_SetVideoMode(ScreenWidth, ScreenHeight, ScreenBPP, ScreenFlags);

    ship = new Entity("images/ship.png", 4, 3, 10);
    asteroid = new Entity("images/asteroid.png", 5, 6, 30);

    asteroid->position.X = (float)(ScreenWidth / 2);
    asteroid->position.Y = (float)(ScreenHeight / 2);
}

Game::~Game()
{
    delete ship;
    delete asteroid;

    SDL_Quit();
}

void Game::OnExecute()
{
    while(!done)
    {
        OnThink();
        OnUpdate();
        OnRender();
    }
}

void Game::OnThink()
{
    while(SDL_PollEvent(&event))
    {
        if(event.type == SDL_QUIT)
            done = true;

        if(event.type == SDL_KEYDOWN)
            if(event.key.keysym.sym == SDLK_ESCAPE)
                done = true;

        if(event.type == SDL_MOUSEMOTION)
        {
            ship->position.X = event.motion.x;
            ship->position.Y = event.motion.y;
        }
    }
}

void Game::OnUpdate()
{
    lastTime = thisTime;
    thisTime = SDL_GetTicks();
    deltaTime = (thisTime - lastTime) / 1000.0;

    ship->Update(deltaTime);
    asteroid->Update(deltaTime);
}

void Game::OnRender()
{
    SDL_FillRect(screen, NULL, 0);

    if(collisionRect.w != 0 && collisionRect.h != 0)
        if(Entity::CheckCollision(ship, asteroid))
            SDL_FillRect(screen, &collisionRect, SDL_MapRGB(screen->format, 255, 0, 0));
        else
            SDL_FillRect(screen, &collisionRect, SDL_MapRGB(screen->format, 0, 255, 0));

    ship->Render(screen);
    asteroid->Render(screen);

    SDL_Flip(screen);
}

int main(int argc, char* args[])
{
    Game game;
    game.OnExecute();

    return 0;
}
Entity.h
#ifndef ENTITY_H
#define ENTITY_H

#include "SDL.h"
#include "SDL_image.h"

struct Vector
{
    float X;
    float Y;

    Vector()
    {
        X = 0.0f;
        Y = 0.0f;
    }
};

class Entity
{
    public:
        Entity(char* filename, int tilesX, int tilesY, double frameRate);
        ~Entity();

    public:
        Vector position;
        Vector origin;

    public:
        SDL_Surface* surface;

        int tilesX;
        int tilesY;
        int tileWidth;
        int tileHeight;

        int frame;
        int frameCount;

        double frameInterval;
        double frameTimeRemaining;

    public:
        void Update(double deltaTime);
        void Render(SDL_Surface* screen);

    public:
        SDL_Rect GetBounds();
        SDL_Rect GetFrameBounds();
        SDL_Rect NormalizeBounds(const SDL_Rect& rect);

        static SDL_Rect Intersection(const SDL_Rect& boundsA, const SDL_Rect& boundsB);
        static bool CheckCollision(Entity* entityA, Entity* entityB);
        static bool GetAlphaXY(Entity* entity, int x, int y);
};

#endif
Entity.cpp
#include "Entity.h"
#define Maximum(a, b) ((a > b) ? a : b)
#define Minimum(a, b) ((a < b) ? a : b)

Entity::Entity(char* filename, int tilesX, int tilesY, double frameRate)
{
    this->surface = IMG_Load(filename);

    this->tilesX = tilesX;
    this->tilesY = tilesY;
    this->tileWidth = surface->w / tilesX;
    this->tileHeight = surface->h / tilesY;
    this->frame = 0;
    this->frameCount = tilesX * tilesY;
    this->frameInterval = 1 / frameRate;
    this->frameTimeRemaining = frameInterval;

    this->origin.X = tileWidth / 2.0f;
    this->origin.Y = tileHeight / 2.0f;
}

Entity::~Entity()
{
    SDL_FreeSurface(surface);
}

void Entity::Update(double deltaTime)
{
    frameTimeRemaining -= deltaTime;
    if(frameTimeRemaining < 0)
    {
        frame++;
        frame %= frameCount;
        frameTimeRemaining = frameInterval;
    }
}

void Entity::Render(SDL_Surface* screen)
{
    SDL_Rect location = GetBounds();
    SDL_Rect clip = GetFrameBounds();

    SDL_BlitSurface(surface, &clip, screen, &location);
}

SDL_Rect Entity::GetBounds()
{
    SDL_Rect bounds;
    bounds.x = (Sint16)(position.X - origin.X);
    bounds.y = (Sint16)(position.Y - origin.Y);
    bounds.w = (Sint16)(tileWidth);
    bounds.h = (Sint16)(tileHeight);

    return bounds;
}

SDL_Rect Entity::GetFrameBounds()
{
    SDL_Rect frameBounds;
    frameBounds.x = (Sint16)(frame % tilesX * tileWidth);
    frameBounds.y = (Sint16)(frame / tilesX * tileHeight);
    frameBounds.w = (Sint16)(tileWidth);
    frameBounds.h = (Sint16)(tileHeight);

    return frameBounds;
}

SDL_Rect Entity::NormalizeBounds(const SDL_Rect& rect)
{
    SDL_Rect normalized;
    normalized.x = rect.x - (Sint16)position.X + (Sint16)origin.X + GetFrameBounds().x;
    normalized.y = rect.y - (Sint16)position.Y + (Sint16)origin.Y + GetFrameBounds().y;
    normalized.w = rect.w;
    normalized.h = rect.h;

    return normalized;
}

SDL_Rect Entity::Intersection(const SDL_Rect& boundsA, const SDL_Rect& boundsB)
{
    int x1 = Maximum(boundsA.x, boundsB.x);
    int y1 = Maximum(boundsA.y, boundsB.y);
    int x2 = Minimum(boundsA.x + boundsA.w, boundsB.x + boundsB.w);
    int y2 = Minimum(boundsA.y + boundsA.h, boundsB.y + boundsB.h);

    int width = x2 - x1;
    int height = y2 - y1;

    if(width > 0 && height > 0)
    {
        SDL_Rect intersect = {x1, y1, width, height};
        return intersect;
    }
    else
    {
        SDL_Rect intersect = {0, 0, 0, 0};
        return intersect;
    }
}

bool Entity::CheckCollision(Entity* entityA, Entity* entityB)
{
    SDL_Rect collisionRect = Intersection(entityA->GetBounds(), entityB->GetBounds());

    if(collisionRect.w == 0 && collisionRect.h == 0)
        return false;

    SDL_Rect normalA = entityA->NormalizeBounds(collisionRect);
    SDL_Rect normalB = entityB->NormalizeBounds(collisionRect);

    for(int y = 0; y < collisionRect.h; y++)
        for(int x = 0; x < collisionRect.w; x++)
            if(GetAlphaXY(entityA, normalA.x + x, normalA.y + y) && GetAlphaXY(entityB, normalB.x + x, normalB.y + y))
                return true;

    return false;
}

bool Entity::GetAlphaXY(Entity* entity, int x, int y)
{
    int bpp = entity->surface->format->BytesPerPixel;
    Uint8* p = (Uint8*)entity->surface->pixels + y * entity->surface->pitch + x * bpp;
    Uint32 pixelColor;

    switch(bpp)
    {
        case(1):
            pixelColor = *p;
            break;
        case(2):
            pixelColor = *(Uint16*)p;
            break;
        case(3):
            if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
                pixelColor = p[0] << 16 | p[1] << 8 | p[2];
            else
                pixelColor = p[0] | p[1] << 8 | p[2] << 16;
            break;
        case(4):
            pixelColor = *(Uint32*)p;
            break;
    }

    Uint8 red, green, blue, alpha;
    SDL_GetRGBA(pixelColor, entity->surface->format, &red, &green, &blue, &alpha);

    return alpha > 200;
}


SDL + OpenGL Tutorial Basics

Beginning OpenGL
In this new tutorial series, we will be looking at using SDL along with OpenGL. SDL as it stands is nice for basic 2D graphics/composition, but when it comes to more intensive gaming applications or 3D graphics, we need to leverage the use of other libraries. SDL works so well with OpenGL because we can use OpenGL to manage only graphical rendering, and leave SDL for everything else (Events, Window management, etc).

So, lets get started. We'll be basing our code off of the "SDL Tutorial Basics" tutorial - so head on over there first if you haven't had a chance to look at it. Primarily what we'll be doing here is making any necessary changes to the code for use with OpenGL. What's nice about SDL is that it provides an easy way to setup OpenGL. On the technical side of things, whenever a window is created on an operating system, an OpenGL context must also be created (with certain properties) and linked to that window. SDL makes this rather easy for us.

Before we get started with modifying our code, open up your project file (the one from SDL Tutorial Basics). Because we are using another library now, we are going to need to link to it. So, open up your Project Properties, and add "opengl32", "glu32" to your linker settings (this is the same area that has mingw32, SDLmain, SDL). If you are another operating system besides Windows, your library names will most likely be "opengl", "glu." Most people already have the OpenGL library installed (as it usually comes with CodeBlocks). If you don't, make sure you downloaded the CodeBlocks with mingw. If you are on some Linux flavor, try installing Mesa. Also, as a last note, put the new libraries after the SDL libraries.

That's it for project settings, lets move on to modifying our code. Open up CApp.h and include our OpenGL header file.
#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>
#include <gl/gl.h>
#include <gl/glu.h>

// ... other code below
If you are wondering what the difference between gl and glu, gl contains the primary functions from the OpenGL specification, and glu contains several helper/utility functions for creating complex shapes, moving the camera, etc. So now that we have that, lets actually get OpenGL up and running. Open up CApp_OnInit.cpp. Right now this is what we have:
#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    return true;
}
The first thing we need to do is tell SDL that we are going to be using OpenGL. Thankfully, SDL provides an easy way to do this. Add the flag SDL_OPENGL just after SDL_DOUBLEBUF, and change SDL_DOUBLEBUF to SDL_GL_DOUBLEBUFFER:
    if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_GL_DOUBLEBUFFER | SDL_OPENGL)) == NULL) {
        return false;
    }
Now, there are several settings you can set for OpenGL. By default, most are fine just as is, but I want to make you aware of them:
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,        8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,      8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,       8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,      8);

SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,      16);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,        32);

SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,    8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,    8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    8);

SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,  1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  2);
These are all basic memory sizes for how much data OpenGL will store (except SDL_GL_MULTISAMPLEBUFFERS/SDL_GL_MULTISAMPLESAMPLES, those are for something else). By increasing these, you will effectively increase how much memory OpenGL will use for color information, and the buffer. As I mentioned before, the default on all of these is usually just fine. If you wish to set these, the values I have up above are pretty good. Be sure to call SDL_GL_SetAttribute before calling SDL_SetVideoMode. One thing I want to mention is on SDL_GL_MULTISAMPLEBUFFERS. This is basically your anti-aliasing flag. By turning this on, you are turning on anti-aliasing. SDL_GL_MULTISAMPLESAMPLES is how much to anti-alias. A value of 2 is pretty standard if turned on, 4 is also commonly used. Just keep in mind that this can be resource intensive for some systems. Also, not every computer is able to perform anti-aliasing. And one last note! Make sure your SDL_GL_DEPTH_SIZE is a power of two (16, 32 works), otherwise anti-aliasing may not work for you.

Moving on. Now we basically have OpenGL setup with our window, but the problem is that OpenGL requires additional information about the rendering area. What I mean by this is that because OpenGL is a 3D rendering library, it needs to know some basic things like where the camera is, what our 3D world looks like, what color is our rendering area, etc.

So, lets add a few more lines of code after SDL_SetVideoMode.
glClearColor(0, 0, 0, 0);
glClearDepth(1.0f);

glViewport(0, 0, 640, 480);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(0, 640, 480, 0, 1, -1);

glMatrixMode(GL_MODELVIEW);

glEnable(GL_TEXTURE_2D);

glLoadIdentity();
Okay, this is where we actually get to start using OpenGL. First, note that OpenGL is a C library (like SDL), and that all function calls begin with gl* (much like all SDL function calls begin with SDL_*). So what are we doing here exactly? The first function call, glClearColor, tells OpenGL what color to render the view when the color buffer is cleared. The color buffer is the buffer OpenGL uses to render pixels to the screen. glClearDepth does something similar. It tells OpenGL what value to reset the depth buffer when it is cleared. The depth buffer is used to determine the z-axis of a pixel. There are other buffers as well, but only worry about these two for now. The 4 arguments are basically Red, Green, Blue, Alpha. With 1.0f being full color, and 0.0f being no color. If you want to define values as regular RGB 255 values, do this:
glClearColor((128.0f / 255.0f), 1.0f, 1.0f, 1.0f); //Cyan color

Next, we setup the viewport for opengl. This, in essence, is the window area OpenGL will use. If you set it smaller to the SDL window size you'll have black borders, and if you set it larger then the SDL window, you'll have your view clipped. Usually, this will always be the same as the regular window size, but there are certain cases you would want it smaller (you can actually create multiple viewports, and have them all display different camera angles of a single scene). Arguments are X, Y, Width, Height.

After that, we tell OpenGL to change to GL_PROJECTION mode. OpenGL contains certain matrix modes that affect certain things. I don't want to confuse you with technical details, but you'll only ever want to use GL_PROJECTION when you are setting up the "projection" of the scene (which is what the glOrtho call does). Other possible projection functions are glLoadIdentity / gluPerspective / glFrustum / gluOrtho2D. Otherwise, you'll almost always want to be in GL_MODELVIEW mode.

So, after we are in projection mode, we call glLoadIdentity (which is basically a "reset matrix" call). Then, we call glOrtho. Now, to take a quick aside for a moment, we have been working in 2D thus far in all the tutorials on this website. This tutorial is no different, so we are going to use glOrtho to setup a "2D Perspective." An ortho, or orthogonal, perspective is one that flattens a 3D environment into a 2D one. This more or less elmiminates any 3D look, and makes it render like a regular 2D game. Please note though, you can still using the z-axis for layering. On the flip side, if you wanted a 3D environment, you would use gluPerspective or similar to setup a "3D Perspective." So, in simple basic terms, for 2D us glOrtho, and for 3D use gluPerspective. Regarding the arguments of the function, they are Left, Right, Bottom, Top, Near, Far. So, by setting Left to 0, and Right to 640, I am defining the perspective area. If you set these values to something different than the window size, it'd basically stretch / shrink everything.

Moving on, we switch to GL_MODELVIEW mode, reset it, and also enable GL_TEXTURE_2D (By default, textures are not enabled). What is a texture? A texture is very much like an SDL_Surface. It's an image stored in memory that can be rendered to the display, but unlike SDL_Surface's, Textures cannot be rendered to the screen on their own. They must be attached to some sort of primitive. Meaning, if you want to render a basic texture like an SDL_Surface, you have to "Bind" the texture (make it active), render a square, and then attach the texture to the four corners of that square. It sounds more complex then it really is, and we'll take a look at doing that in future tutorials.

This is how our OnInit methods ends up looking like:
bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,            8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,          8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,           8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,          8);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,          16);
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,            32);

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,        8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,        8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    8);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,  1);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  2);

    if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_GL_DOUBLEBUFFER | SDL_OPENGL)) == NULL) {
        return false;
    }

    glClearColor(0, 0, 0, 0);

    glViewport(0, 0, 640, 480);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, 640, 480, 0, 1, -1);

    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_TEXTURE_2D);

    glLoadIdentity();

    return true;
}

Okay, that's it for our OpenGL setup. Please note that there are other setup options I haven't mentioned, but I am sure we will get to them in future tutorials.

Jump on over to OnRender and lets get something to show up on the screen.
void CApp::OnRender() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glBegin(GL_QUADS);
        glColor3f(1, 0, 0); glVertex3f(0, 0, 0);
        glColor3f(1, 1, 0); glVertex3f(100, 0, 0);
        glColor3f(1, 0, 1); glVertex3f(100, 100, 0);
        glColor3f(1, 1, 1); glVertex3f(0, 100, 0);
    glEnd();

    SDL_GL_SwapBuffers();
}

We should be able to get through this fairly quickly. The first line clears our color and depth buffer. For all simplistic purposes, this does a type of reset, and makes our screen blank again. This is similar to calling SDL_FillRect on SDL's primary display surface. Keep in mind though it does more than that. Then, we reset our GL_MODELVIEW matrix. Why? Whenever you move around in the 2D / 3D world in OpenGL to render things you are modifying the model view matrix. This matrix is what OpenGL uses to determine where and how to render something to the screen. So, if you are moving around rendering things, you need to reset it on every loop.

Next is where the magic starts to happen. glBegin is used to signal the start of the rendering of a primitive. The parameter is used to simply define what primitive you are going to render. In this case, we are going to render a Rectangle (called a Quad - 4 sides). glEnd is used to signal the end you rendering that primitive.

Now, when we go to render our primitive what we have to do is define each corner of that primitive. So, we start with the first corner (or vertex), specify the coordinates, and then move to the next, and so on. At the same time, I am defining the colors of each corner (each corner will have a different color). What OpenGL will do for us is color the entire primitive blending those four corner's colors together. It ends up looking like this:

Lastly, we call SDL_GL_SwapBuffers which basically takes the buffer and moves it to the primary display - much like SDL_Flip.

And that's it! I hope you see how easy it is to setup OpenGL. There's quiet a bit to memorize at first, but once you have it down, it's not that bad at all.

If you want more information about specific functions, check out the docs here:
http://www.opengl.org/sdk/docs/man/


SDL TTF

Fonts, and how to use them

Text is an important part of almost all games. From informing the player of the awesome combo that they just got, to menu titles, button captions and epic storylines. In this tutorial, we will take a look at SDL_ttf, an addon library for SDL that uses the FreeType library to load fonts and render text. SDL_ttf adheres to the C-style syntax of SDL, so if you are familiar with SDL (which you hopefully are) it won't be too hard to get used to SDL_ttf's calling conventions. You can get the newest version as of this writing (2.0.10-1 to be exact) here.

Let's start by defining some font terms which we will use throughout this tutorial:

Font face: A style of a font. For example bold or italic. Font files can contain more than one font face.
Point size: The font measure originally used in typography. Some but not all digital fonts use point sizes and one of these is roughly 1/72 of an inch (~2.54cm), but this is by no means a guarantee.
Glyph: A single character of a font, like 'a'.

Initialization, shutdown and library versions

Loading and unloading SDL_ttf is very similar to that of SDL. All SDL_ttf functions are prefixed with 'TTF_'. Before we can use any of these functions, however, we need to initialize the library:
if (TTF_Init() < 0) {
    // Handle error...
}
TTF_Init() returns -1 if an error occurred and zero if the initialization was successful. Similar to SDL_WasInit(...), you can check if SDL_ttf was successfully initialized like this:
int was_init = TTF_WasInit();

if (was_init == 1)
    // SDL_ttf was already initialized
else if (was_init == 0)
    // SDL_ttf was not already initialized
When you are done with your sweet text-rendering business, SDL_ttf is shut down like this:
void TTF_Quit();
Alternatively, you can use the atexit(...) function to ensure it is automatically called when the program terminates:
#include <cstdlib> // The function is defined in this header

// ...

atexit(TTF_Quit); // Ensure TTF_Quit() is called when we exit
You can also check the version of the linked SDL_ttf library you are using at runtime. Unlike the other functions provided by SDL_ttf, these two do not need any prior initialization to be used:
const SDL_version *TTF_Linked_Version();
void SDL_TTF_VERSION(SDL_version *compile_version);
Here's an example:
#include <iostream>

const SDL_version *linked_version = TTF_Linked_Version();
SDL_version compiled_version;
SDL_TTF_VERSION(&compiled_version);

std::cout << "Linked version:\n"
        << linked_version->major << "." << linked_version->minor << "." << linked_version->patch;

std::cout << "Compiled version:\n"
        << compiled_verison.major << "." << compiled_version.minor << "." << compiled_version.patch << std::endl << std::endl;
Note: I am using the standard I/O (input/output) library to print text to the console window. This gives the same result as using the C function printf(...)

Opening and closing fonts

SDL_ttf uses a single struct to represent its fonts: TTF_Font. The documentation warns not to directly access its contents as they may change in future releases and deprecate projects using older versions. Use the available functions instead. SDL_ttf supports loading of TrueType fonts (with the '.ttf' extension) and opening a font is quite simple. There are four ways of doing it and this is the first one:
TTF_Font* TTF_OpenFont(const char* file, int ptsize);
The first function takes a TrueType font file and a point size and returns the loaded TTF_Font pointer or NULL if loading failed. The point size is a somewhat vague term as mentioned earlier on. It used to be a measure of the height of the metal frame of characters used in typography in olden times, but in digital fonts it is a bit more complicated. I suggest that you experiment with the size until you get the one you want instead of worrying about point sizes because you seldom know what size you want beforehand anyways. If you still want to know about point sizes, take a look in the suggested reading section at the bottom of this tutorial.

The second function looks like this:
TTF_Font* TTF_OpenFontIndex(const char* file, int ptsize, long index);
It is identical to the previous function apart from the last argument. This function loads a specific font face from the file, and is useful if your font file contains several font faces. A font index of 0 is always the first font in the file.

The third function is:
TTF_Font* TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize);
You may or may not have seen the SDL_RWops struct before. It is an undocumented feature of SDL and so is rarely seen (I personally have never seen nor used it). It is capable of taking a pointer to memory where some data is stored, but files are supported too. Since this tutorial is about SDL_ttf and not SDL_RWops, I've added a suggested reading entry at the bottom if you want to know more. If you pass a non-zero value as the second argument the memory used by the SDL_RWops struct is automatically freed before the function returns. The third and last argument is the point size.

The final function does the same thing as the previous one, but loads a specific font face from the SDL_RWops struct.
TTF_Font* TTF_OpenFontIndexRW(SDL_RWops *src, int freesrc, int ptsize, long index);
Just like SDL_Surfaces, there is a single function to free a TTF_Font:
void TTF_CloseFont(TTF_Font *font);
When a function fails to load, you may use TTF_SetError and TTF_GetError just as you use the equivalent functions of SDL:
void TTF_SetError(const char *fmt, ...)
char *TTF_GetError()
These functions are actually defined macros to SDL_SetError(...) and SDL_GetError() and so work just like them:
if (/* loading failed */) {
    TTF_SetError("Loading failed :( (code: %d)", 142);
    std::cout << "Error: " << TTF_GetError() << std::endl;
}

Rendering fonts

Let's get to the rendering part and get some cool fonts onto the screen. There are twelve functions int total available for rendering which I have chosen to split into three categories: Solid-, shaded- and blended rendering. Here are the first four for solid rendering:
SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font* font, Uint16 ch, SDL_Color fg);
SDL_Surface *TTF_RenderText_Solid(TTF_Font *font, const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font, const Uint16 *text, SDL_Color fg);

Solid rendering is the fastest type of text rendering but gives you the lowest quality. It is best used for text that is expected to change frequently. The first function renders a single glyph while the others render full text strings. However, the last two functions render with text encoding. If you are unfamiliar with the UNICODE standard or UTF-8, I've added entries in the suggested reading section, but I'll give a short introduction to both of them. UNICODE is a standard for text encoding. It defines a set of rules for characters. UNICODE text encoding can be used with different character encodings, one of the most popularly being UTF-8. UTF stands for Unicode transformation format, and the number denotes the bit-size used for the encoding. For example, the character 'T' encodes as U+0054. This is properly a bit confusing, so I advise you to go read the suggested reading entries for a more thorough explanation.

Next up is shaded rendering:

SDL_Surface *TTF_RenderGlyph_Shaded(TTF_Font* font, Uint16 ch, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUNICODE_Shaded(TTF_Font *font, const Uint16 *text, SDL_Color fg, SDL_Color bg);
Shaded rendering gives a nicer anti-aliased result, but is slower. Additionally, the text will have a box around it. The color of this box is the bg (background) color you pass to the function. If you're unsure what anti-aliased means, it's when an image (like text) is surrounded at its edges by pixels shaded roughly the same color as that part of the image. This results in a more smooth and nicer-looking image. Here's an example to explain what I mean:

Text with and without anti-aliasing

The last four functions are:
SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font, Uint16 ch, SDL_Color fg);
SDL_Surface *TTF_RenderText_Blended(TTF_Font *font, const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font, const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font, const Uint16 *text, SDL_Color fg);
Blended rendering yields the best results and as you might suspect it is the slowest of all the rendering functions though not really that much slower relative to shaded rendering. The returned SDL_Surface is a 32-bit ARGB surface with alpha transparency and anti-aliasing. Because this surface will blit slower than either solid or shaded rendered surfaces, you should use it for text that isn't expected to change a lot.

Here's an image of three types of rendering so you can see the differences:

Solid, shaded and blended rendering

Font properties

Fonts have properties and attributes that you can both set and retrieve. One is the font style. Bold, italic and underline are examples of a font style. Additionally, the newest version of SDL_ttf provides the strikethrough style as well, which puts a line right through the middle of the text. To set a font's style you use the following function:
void TTF_SetFontStyle(TTF_Font *font, int style);
The first argument is a pointer to the font for which you wish to change the style. The second is an integer that denotes which style you want to change to. This argument can be any of the below:
    TTF_STYLE_BOLD
    TTF_STYLE_ITALIC
    TTF_STYLE_UNDERLINE
    TTF_STYLE_STRIKETHROUGH
    TTF_STYLE_NORMAL
These are all bitmasks and so can be combined just like the entity flags used in the SDL Collision tutorial:
TTF_SetFontStyle(myFont, TTF_STYLE_BOLD | TTF_STYLE_UNDERLINE);
Use TTF_STYLE_NORMAL to reset all styles applied to a font. As you may expect, there's an accompaniying get function for getting the styles applied to a font:
int TTF_GetFontStyle(TTF_Font *font);
This returns the bitmask flags set for the font you pass to the function. It returns TTF_STYLE_NORMAL if no style applies. If you don't know how to use bitmasks, fear not! This is how:
int myFontStyles = TTF_GetFontStyle(myFont);

if (myFontStyles & TTF_STYLE_BOLD) {
    // Font is bold
}
If you want your font to have an outline, that's possible too. An outline is an additional border around the font. Here's an example:

Example of an outlined font

Just as with most of these functions, there is a set/get function pair available for font outlines as well:
void TTF_SetFontOutline(TTF_Font *font, int outline);
int TTF_GetFontOutline(TTF_Font *font);
The second parameter to TTF_SetFontOutline is the outline width in pixels. Passing zero turns outlines off. The second function simply returns the current outline width.

SDL_ttf also allows you to turn kerning on and off. What is kerning you ask? Let's look at a string like "AV". Here's what it will look like with and without kerning:

Text with and without kerning

See the difference? In the first image, the 'V' is rendered besides the 'A' such that you could draw a line between the letters that will just touch the edge of each letter. In the second, the letters have been moved closer together. This essentially "compresses" the string because it uses the "free space" that is not available without kerning. Note that this is not always possible everytime. For example, in the string "AA" you can't move either 'A' further towards each other.

The SDL_ttf set/get function pair is:
void TTF_SetFontKerning(TTF_Font *font, int allowed);
int TTF_GetFontKerning(TTF_Font *font);
Passing zero to the second parameter of TTF_SetFontKerning disables kerning. A non-zero integer value turns kerning on. The second function returns zero if kerning is disabled and a non-zero value if kerning is on.

Note: For newly loaded fonts, the default kerning is 1, i.e. on.

Now we take a look at some functions that deal more with the actual physical size of the text:
int TTF_FontHeight(const TTF_Font *font);
int TTF_FontAscent(const TTF_Font *font);
int TTF_FontDescent(const TTF_Font *font);
int TTF_FontLineSkip(const TTF_Font *font);
int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h);
Refer to this picture for glyph metric conventions:

Glyph metric convention

Let's work our way through all these functions from top to bottom. The first function returns the maximum pixel height of the glyphs loaded for the particular font. That is, the pixel height of the tallest glyph contained within the font.

The second returns the maximum pixel ascent of the font. The ascent is the distance from the top of the glyph to its baseline.

The third function returns the maxmum pixel descent of the font. This is the distance from the baseline to the bottom of the font.

Although you can use TTF_FontHeight to space multi-line text, it is better to provide some space between each line. This space is what the fourth function TTF_FontLineSkip returns.

Note: TTF_FontLineSkip returns only a suggested value for the size of the line gap. Not necessarily the "correct" one.

The final three functions return information about the size of a text string if it was rendered. TTF_SizeText fills the w and h variables with the correct values. The function returns zero if the third and fourth parameters were successfully filled, -1 otherwise. The other two functions with similar names do the same but for UTF8- and UNICODE-encoded strings.

Here's an example for the first function:
#include <iostream>

int w = 0;
int h = 0;

if ((TTF_SizeText(myFont, "SDL_ttf is awesome!", &w, &h) != -1) {
    // Print out the width and height of the string if I render it with myFont
    std::cout << "Width : " << w << "\nHeight: " << h << std::endl;
} else {
    // Error...
}
Next is font hinting. Font hinting "hints" to SDL_ttf how to draw the font using mathematical formulas. The proper details are beyond the scope of this tutorial though.
void TTF_SetFontHinting(TTF_Font *font, int hinting);
int TTF_GetFontHinting(TTF_Font *font);
The second parameter of TTF_SetFontHinting can be either of the following:
    TTF_HINTING_NORMAL
    TTF_HINTING_LIGHT
    TTF_HINTING_MONO
    TTF_HINTING_NONE
The documentation advises you to experiment with these settings, as do I. Be careful though! Calling this function flushes the internal cache of pre-rendered glyphs. This is true even if you pass the same hinting parameter already set for the font. Therefore, you should check the font's hinting parameter with TTF_GetFontHinting before you decide to change it.

There are also a couple of functions available if you want to get some information about the font face you are using:
long TTF_FontFaces(const TTF_Font *font);
int TTF_FontFaceIsFixedWidth(const TTF_Font *font);
char * TTF_FontFaceFamilyName(const TTF_Font *font);
char * TTF_FontFaceStyleName(const TTF_Font *font);
The first function returns the number of font faces contained in the given font.
The second function returns zero if the font face is not fixed width and a value above zero if it is. A font with fixed width means that all its characters (glyphs) have the exact same width.
The third gets the family name of a font. These can be names like: Arial or Verdana that you use in a word processor.
The fourth function returns the name of this font's style. Some fonts are all in bold or italic and this is what you'll get from this function. If this function returns NULL, no information could be retrieved.

As for information about glyphs, SDL_ttf provides these two:
int TTF_GlyphIsProvided(const TTF_Font *font, Uint16 ch);
int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch, int *minx, int *maxx, int *miny, int *maxy, int *advance);
The first tells you whether or not the given character is available for the font:
int temp = TTF_GlyphIsProvided(myFont, 'a');

if (temp)
    // glyph is available :D
else
    // glyph is unavailable :(
Note that if the glyph is available the returned integer is actually the glyph index in the font.

The second function fills in minx, maxx, miny, maxy and advance with the proper values if possible. These values correspond those on the image I showed you earlier for glyph metric conventions. Here's an example:
int minx = 0;
int maxx = 0;
int miny = 0;
int maxy = 0;
int advance = 0;

int success = TTF_GlyphMetrics(myFont, 'a', &minx, &maxx, &miny, &maxy, &advance);

if (success == 0) {
    // Yay! No errors
    std::cout << "Min X: " << minx
            << "\nMax X: " << maxx
            << "\nMin Y: " << miny
            << "\nMax Y: " << maxy
            << "\nAdvance: " << advance << std::endl;
} else if (success == -1) {
    // Ohoh...
}
If the function returns -1 for failure, your values get filled with NULL, so watch out!

And so we now take a look at the last function of the SDL_ttf, phew!
void TTF_ByteSwappedUNICODE(int swapped);
This function tells SDL_ttf if UNICODE text is byte swapped or not. What? Well, this has to do with the endianness of your computer. Umm...what? Okok, without going into too much detail, endianness deals with the ordering of bytes in a computer. Hence another word for endianness is "byte order". There's an entry in suggested reading section about endianness.

As a final note, almost all of SDL_ttf's functions that take a pointer to a TTF_Font cause a segfault error if you pass NULL instead, so check the validity of your font pointer before you use it.
if (myFont)
    // Font is OK!
else
    // Font is not OK!

Entering text

Here's a quick example of how to get text entered by the user, that I picked up a while back from the Lazyfoo SDL tutorial site.
std::string text = "";
SDL_EnableUNICODE(SDL_ENABLE);

void OnKeyDown(SDLKey key, SDLMod modifier, Uint16 unicode) {

    if(key == SDLK_BACKSPACE) {
        if(!text.empty())
            text.erase(text.name.length() - 1);
    } else if(key == SDLK_RETURN) {
        if(!text.empty()) {
            // Do stuff with the text
        }
    } else {
        if(text.length() < MAX_NAME_SIZE) {
            if(unicode == ' ' ||
               unicode >= 'a' && unicode <= 'z' ||
               unicode >= 'A' && unicode <= 'Z' ||
               unicode >= '0' && unicode <= '9')
                text.name += (char)unicode;
        }
    }
}

SDL_EnableUNICODE(SDL_DISABLE);

Closing words

And that's all the functions provided by SDL_ttf. Hopefully, you now understand how to use the library or discovered some interesting things you didn't know about it before. I wish you good luck in all your future font-related endeavours :)

As an added bonus, I've included a small sample for you where you can try some the things we've talked in the tutorial for yourself. Enter the directory path of the font you want to load in the "fontname.txt" file and try it out. The sample has only been tested on a Windows computer, but should hopefully work on the other platforms. The controls are:

[s]: Solid rendering
[d]: Shaded rendering
[b]: Blended rendering
[k]: Toggle kerning
[o] + [0-3]: Set outline width
[Esc]: Quit the application

This is my first tutorial ever! So please bear with me if there are some mistakes or pecularities in the text. Hopefully you enjoyed it anyway! I would appreciate any feedback you guys have on how to improve, including any corrections.

Suggested reading

SDL_ttf documentation
UNICODE article entry on Wikipedia
UTF-8 article entry on Wikipedia
SDL_RWops
Endianness

Downloadable fonts (always check how "free" fonts are):
http://www.1001freefonts.com/
http://www.urbanfonts.com/
http://www.dafont.com/


SDL App States

Or Game States, whatever you prefer
In this muchly desired side tutorial we are going to look at how to implement something most commonly called Game States (or as I call them, App States). This tutorial will be based upon the code from the SDL Collision Events tutorial.

To simply explain what an app state is, try to think of your game (or application) as a parent with many child applications. In other words, your primary application can yield control to a single child when needed.

This is probably best explained with an example. Say we are working on a typical game - like a simple side scroller (such as Mario). What would the different application states be in this case?

1. Splash Screen
2. Menu
3. World Overview
4. Game
5. Credits

So, to tie into my previous explanation, when the parent application launches it first loads the child app state Splash Screen and yields control to it. This allows the Splash Screen to render a neat graphic, animate, and do its thing. Once it's done, it turns over control to the Menu. The menu then captures input events, and allows the user to make some choices. If the user clicks Play (or whatever), the menu app state will then yield control over to the World Overview app state. I think you get the picture.

The basic premise of app states is to turn over control to another app state when certain criteria is met (usually a timer or input event).

Just by understanding what's above, we could simply have a pointer that points to the active app state, and then switch between objects. And that does work. But we're going to take that a step further and introduce what I call an App State Manager. The purpose of the manager is to handle switching between app states. Why would we want something to manage that? Well, for example, when we switch between a splash screen and the main menu we could do some neat fade out / fade in effect. Without an app state manager, that would still be possible, but not nearly as neat.

Here's how that class would look (CAppStateManager.h):
#ifndef __CAPPSTATEMANAGER_H__
    #define __CAPPSTATEMANAGER_H__

#include "CAppState.h"

enum {
    // Add your Other App States Here
    APPSTATE_NONE,
    APPSTATE_INTRO,
    APPSTATE_GAME
};

class CAppStateManager {
    private:
        static CAppState* ActiveAppState;

    public:
        static void OnEvent(SDL_Event* Event);

        static void OnLoop();

        static void OnRender(SDL_Surface* Surf_Display);

    public:
        static void SetActiveAppState(int AppStateID);

        static CAppState* GetActiveAppState();
};

#endif
Notice that every member is static. Why? Basically because this class is a singleton, and we I want it easy to use. So, instead of coding something like CAppStateManager::GetInstance()->OnRender(), we can simply do CAppStateManager::OnRender(). Which looks better to you?

Notice the enum and the 3 App States that I created. We are going to create an Intro App State and Game App State for this tutorial. When you want to add more app states, you'll need to add them to this enum.

So lets continue and implement the rest of the code for this class (CAppStateManager.cpp).
#include "CAppStateManager.h"

// Refer to your Other App States Here
#include "CAppStateIntro.h"
#include "CAppStateGame.h"

CAppState* CAppStateManager::ActiveAppState = 0;

void CAppStateManager::OnEvent(SDL_Event* EventHolder) {
    if(ActiveAppState) ActiveAppState->OnEvent(EventHolder);
}

void CAppStateManager::OnLoop() {
    if(ActiveAppState) ActiveAppState->OnLoop();
}

void CAppStateManager::OnRender(SDL_Surface* Surf_Display) {
    if(ActiveAppState) ActiveAppState->OnRender(Surf_Display);
}

void CAppStateManager::SetActiveAppState(int AppStateID) {
    if(ActiveAppState) ActiveAppState->OnDeactivate();

    // Also, add your App State Here so that the Manager can switch to it
    if(AppStateID == APPSTATE_NONE)        ActiveAppState = 0;
    if(AppStateID == APPSTATE_INTRO)    ActiveAppState = CAppStateIntro::GetInstance();
    if(AppStateID == APPSTATE_GAME)        ActiveAppState = CAppStateGame::GetInstance();

    if(ActiveAppState) ActiveAppState->OnActivate();
}

CAppState* CAppStateManager::GetActiveAppState() {
    return ActiveAppState;
}
One important side note before we continue, notice how I am including the other app state classes. Just like in the header file, you'd want to include the header files for your other app states there, as well as inside the SetActiveAppState method. But, more importantly, notice how I am including the other app states inside the .cpp file instead of the header file. This is to avoid something called circular dependencies. When we begin to creat the other app state files we are going to include this CAppStateManager class. If the CAppStateManager class was including the header files for the other app states inside its header, you'd run into an include loop.

To better explain this:
#ifndef __CAPPSTATEMANAGER_H__
    #define __CAPPSTATEMANAGER_H__

#include "CAppStateIntro.h"

class CAppStateManager {
    // Other Code
};

#endif

#ifndef __CAPPSTATEINTRO_H__
    #define __CAPPSTATEINTRO_H__

#include "CAppStateManager.h"

class CAppStateIntro {
    // Other Code
};

#endif
Do you see the issue? Each is including the other and the includes will never end (This statement is not quite right. Because of the #define's I have in each header file, the files will only be compiled once in the project. However, you'll still run into issues, so the following still applies.). To remedy this we refer to the class we need inside the .cpp file.

Lets continue on and go through this class line by line. First we have a static member that refers to the current active app state. This pointer will always point to some other app state class. It's what we'll use when we want an app state to be active.

Next we have the familiar OnEvent, OnLoop, and OnRender methods. Inside those all we're simply doing is calling the respective method of the active app state. So, if the Intro app state is active, we'll let the OnEvent, OnLoop, and OnRender methods of that app state run.

Next we have SetActiveAppState which will simply do what it says, set the active app state. We pass an enum value to it from our header file. We then let the active app state know it's being deactivated. Then, find the appropriate app state matched to the enum value, and let it know it's being activated. Simple enough.

Lastly we have a simple method to retrieve the active app state.

Now that we have our manager class up and working, lets go ahead and create some app states. First, we're going to need to create a class that all our app states can inherit from. Appropriately, we'll call this class CAppState (CAppState.h):
#ifndef __CAPPSTATE_H__
#define __CAPPSTATE_H__
#include "CEvent.h"

class CAppState : public CEvent {
    public:
        CAppState();

    public:
        virtual void OnActivate() = 0;

        virtual void OnDeactivate() = 0;

        virtual void OnLoop() = 0;

        virtual void OnRender(SDL_Surface* Surf_Display) = 0;
};

#endif
The class itself is pretty simple. All we're doing is laying out the common methods for an app state, and requiring those to be defined (that's what the '= 0' does, it requires a virtual method to be defined in a child class). Also notice that the CAppState class is inheriting CEvent.

Lets look inside the class (CAppState.cpp):
#include "CAppState.h"

CAppState::CAppState() {
}
Now, don't think about this too hard. Remember all the methods are virtual and required to be defined by classes that inherit CAppState. So, we don't really have to define much of anything inside the class itself.

Okay, now lets actually create our two app states. We'll create our Intro app state first (CAppStateIntro.h).
#ifndef __CAPPSTATEINTRO_H__
    #define __CAPPSTATEINTRO_H__

#include "CAppState.h"
#include "CSurface.h"

class CAppStateIntro : public CAppState {
    private:
        static CAppStateIntro Instance;

        SDL_Surface* Surf_Logo;

        int StartTime;

    private:
        CAppStateIntro();

    public:
        void OnActivate();

        void OnDeactivate();

        void OnLoop();

        void OnRender(SDL_Surface* Surf_Display);

    public:
        static CAppStateIntro* GetInstance();
};

#endif
This class should be pretty straightforward. Remember, in the end, our app states are going to look and behave much like our regular CApp class. So, first we have our instance of this class (which the CAppStateManager uses to set the active app state), a logo to display, and StartTime variable. We'll be using the StartTime variable to do a simple timer to show the splash and then advance to the next app state. You can grab the Logo from the Files area on the sidebar.

Here's inside the class (CAppStateIntro.cpp).
#include "CAppStateIntro.h"

#include "CAppStateManager.h"

CAppStateIntro CAppStateIntro::Instance;

CAppStateIntro::CAppStateIntro() {
    Surf_Logo = NULL;
}

void CAppStateIntro::OnActivate() {
    // Load Simple Logo
    Surf_Logo = CSurface::OnLoad("splash.png");

    StartTime = SDL_GetTicks();
}

void CAppStateIntro::OnDeactivate() {
    if(Surf_Logo) {
        SDL_FreeSurface(Surf_Logo);
        Surf_Logo = NULL;
    }
}

void CAppStateIntro::OnLoop() {
    if(StartTime + 3000 < SDL_GetTicks()) {
        CAppStateManager::SetActiveAppState(APPSTATE_GAME);
    }
}

void CAppStateIntro::OnRender(SDL_Surface* Surf_Display) {
    if(Surf_Logo) {
        CSurface::OnDraw(Surf_Display, Surf_Logo, 0, 0);
    }
}

CAppStateIntro* CAppStateIntro::GetInstance() {
    return &Instance;
}
Inside of OnActivate we load our logo, and then start the timer. Inside of OnDeactivate we free our logo. Inside of OnLoop all we're going is checking if 3 seconds have passed; if so, we change the app state. Inside of OnRender we simply blit the logo. And, of course, our GetInstance returns the Instance to the class.

Lets create your Game app state now (CAppStateGame.h).
#ifndef __CAPPSTATEGAME_H__
#define __CAPPSTATEGAME_H__
#include "CAppState.h"
#include "CArea.h"
#include "CCamera.h"
#include "CEntity.h"
#include "CSurface.h"
#include "CPlayer.h"

class CAppStateGame : public CAppState {
    private:
        static CAppStateGame Instance;

        CPlayer    Player;
        CPlayer    Player2;

    private:
        CAppStateGame();

    public:
        void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);

        void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode);

    public:
        void OnActivate();

        void OnDeactivate();

        void OnLoop();

        void OnRender(SDL_Surface* Surf_Display);

    public:
        static CAppStateGame* GetInstance();
};

#endif
Lots more going on here? Not really, I want you to notice that I simply moved over a lot of the code from CApp.h over to this new file. This is what our CApp.h looks like now:
#ifndef _CAPP_H_
#define _CAPP_H_
#include <SDL.h>
#include "Define.h"
#include "CAppStateManager.h"
#include "CFPS.h"
#include "CEvent.h"
#include "CSurface.h"

class CApp : public CEvent {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

            void OnExit();

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
I basically moved over OnKeyDown, OnKeyUp, the Player objects, and some #include directives (you may notice though that I added #include "CAppStateManager.h" to CApp.h).

Lets create the code for inside our class now (CAppStateGame.cpp).
#include "CAppStateGame.h"

CAppStateGame CAppStateGame::Instance;

CAppStateGame::CAppStateGame() {
}

void CAppStateGame::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_LEFT: {
            Player.MoveLeft = true;
            break;
        }

        case SDLK_RIGHT: {
            Player.MoveRight = true;
            break;
        }

        case SDLK_SPACE: {
            Player.Jump();
            break;
        }

        default: {
        }
    }
}

void CAppStateGame::OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_LEFT: {
            Player.MoveLeft = false;
            break;
        }

        case SDLK_RIGHT: {
            Player.MoveRight = false;
            break;
        }

        default: {
        }
    }
}

void CAppStateGame::OnActivate() {
    if(CArea::AreaControl.OnLoad("./maps/1.area") == false) {
        return;
    }

    if(Player.OnLoad("yoshi.png", 64, 64, 8) == false) {
        return;
    }

    if(Player2.OnLoad("yoshi.png", 64, 64, 8) == false) {
        return;
    }

    Player2.X = 100;

    CEntity::EntityList.push_back(&Player);
    CEntity::EntityList.push_back(&Player2);

    CCamera::CameraControl.TargetMode = TARGET_MODE_CENTER;
    CCamera::CameraControl.SetTarget(&Player.X, &Player.Y);
}

void CAppStateGame::OnDeactivate() {
    CArea::AreaControl.OnCleanup();

    for(int i = 0;i < CEntity::EntityList.size();i++) {
        if(!CEntity::EntityList[i]) continue;

        CEntity::EntityList[i]->OnCleanup();
    }

    CEntity::EntityList.clear();
}

void CAppStateGame::OnLoop() {
    for(int i = 0;i < CEntity::EntityList.size();i++) {
        if(!CEntity::EntityList[i]) continue;

        CEntity::EntityList[i]->OnLoop();
    }

    //Collision Events
    for(int i = 0;i < CEntityCol::EntityColList.size();i++) {
        CEntity* EntityA = CEntityCol::EntityColList[i].EntityA;
        CEntity* EntityB = CEntityCol::EntityColList[i].EntityB;

        if(EntityA == NULL || EntityB == NULL) continue;

        if(EntityA->OnCollision(EntityB)) {
            EntityB->OnCollision(EntityA);
        }
    }

    CEntityCol::EntityColList.clear();
}

void CAppStateGame::OnRender(SDL_Surface* Surf_Display) {
    SDL_Rect Rect;
    Rect.x = 0;
    Rect.y = 0;
    Rect.w = WWIDTH;
    Rect.h = WHEIGHT;

    SDL_FillRect(Surf_Display, &Rect, 0);

    CArea::AreaControl.OnRender(Surf_Display, -CCamera::CameraControl.GetX(), -CCamera::CameraControl.GetY());

    for(int i = 0;i < CEntity::EntityList.size();i++) {
        if(!CEntity::EntityList[i]) continue;

        CEntity::EntityList[i]->OnRender(Surf_Display);
    }
}

CAppStateGame* CAppStateGame::GetInstance() {
    return &Instance;
}
You'll notice all the same things from our Intro class, and you'll also notice all the code that used to be inside of the CApp_* files. OnActivate code was taken from CApp_OnInit, OnLoop code was taken from CApp_OnLoop, OnRender code was taken from CApp_OnRender, and OnDeactivate code was taken from CApp_OnCleanup. We also moved over the code for the keyboard controls for Yoshi. We moved all the code over because it's the job of the Game app state now to actually run the game.

So, lets take a look at the CApp_* files.

CApp_OnInit:
#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(WWIDTH, WHEIGHT, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    SDL_EnableKeyRepeat(1, SDL_DEFAULT_REPEAT_INTERVAL / 3);

    CAppStateManager::SetActiveAppState(APPSTATE_INTRO);

    return true;
}
CApp_OnEvent:
#include "CApp.h"

void CApp::OnEvent(SDL_Event* Event) {
    CEvent::OnEvent(Event);

    CAppStateManager::OnEvent(Event);
}

void CApp::OnExit() {
    Running = false;
}
CApp_OnLoop:
#include "CApp.h"

void CApp::OnLoop() {
    CAppStateManager::OnLoop();

    CFPS::FPSControl.OnLoop();

    char Buffer[255];
    sprintf(Buffer, "FPS: %d", CFPS::FPSControl.GetFPS());
    SDL_WM_SetCaption(Buffer, Buffer);
}
CApp_OnRender:
#include "CApp.h"

void CApp::OnRender() {
    CAppStateManager::OnRender(Surf_Display);

    SDL_Flip(Surf_Display);
}
CApp_OnCleanup:
#include "CApp.h"

void CApp::OnCleanup() {
    CAppStateManager::SetActiveAppState(APPSTATE_NONE);

    SDL_FreeSurface(Surf_Display);
    SDL_Quit();
}
All of this should be self-explanatory, but to help you out, all we're doing really is calling the appropriate method inside of the CAppStateManager class. This will in turn call the appropriate class inside of the app state itself.

And that's it! Try making the splash screen fade, jump, or do other crazy things. Also, try adding animations for when app states change.

x


SDL Tutorial Basics

We all have to start somewhere
These tutorials are meant to teach someone with some experience using C++, or perhaps another programming language. If you have a hard time following certain ideas related to the code itself and not conceptual ideas (those relating to games), then I urge you to first read our tutorials that explain the C++ programming language. It's not vital to understand everything about C++, but every little bit will help later on.

In these tutorials we will be referncing to CodeBlocks as our IDE of choice, with gcc and mingw for compilation. If you wish to use another IDE and compiler that is up to you, but it may be harder to follow along if you have limited experience with linking libraries. If you wish to download CodeBlocks, you can do so for free at http://www.codeblocks.org (download the one including the mingw package). We recommend you use the stable version, unless you want to spend the extra time and use the nightly builds.

These tutorials will be heavily centered around SDL (Simple DirectMedia Layer), a 2D cross-platform graphics library. This library is going to allow us to draw fancy graphics to the screen, and do all sorts of fun stuff that allows us to make a game. You're going to need to download this library at http://www.libsdl.org; be sure to download the Mingw32 tar file under "Development Libraries" and "Win32" and the zip file under "Runtime Libraries" and "Win32." If you are using Visual Studio download the appropriate version instead of the Mingw32 file. Once downloaded, we recommend you put the .dll found in the zip in your system32 folder. This way whenever we open an SDL application it can find the dll file it needs.

Open the tar file now (the one that was listed under "Development Libraries") and decompress the contents to a folder (such as C:\SDL). Now, open up CodeBlocks so we can change some settings. Click "Settings" on the menu bar, and then click "Search Directories" tab. You'll need to add C:\SDL\include to the "Compiler" tab, and C:\SDL\lib to the "Linker" tab (change C:\SDL to wherever you decompressed the files to). Once done, click Okay.

Important Note: I use #include <SDL.h> below (and in all other tutorials), when it really should be #include <SDL/SDL.h>. I moved all of my header files up a directory. So, if you want to follow my code exactly, move all your header files up a directory so they are in the include folder, not the SDL folder. Otherwise, use #include instead. Sorry for all the confusion this has caused.

Start a new "blank" project, calling it whatever you want. Save it some place. Click on "Project" on the menu bar, and then click on "Properties." A dialog will open; click on the "Project's build options..." button on the bottom right. Click on the "Linker Settings" tab and add the following to the list under "Link libraries":

mingw32
SDLmain
SDL

The order is important, so use the arrows to rearrange the list until it matches what is shown above. If you are having a little trouble figuring out what we are doing, we're simply "linking" code together, or, in other words, we're taking the SDL code and combining it with our own. We do this by using the include files to compile, and the lib files to link. And once done, our code is put together to make one application.

Click Ok twice, and you should be all set to go!

Let's create two new files called CApp.h, and CApp.cpp; these will serve as the core for our program. First, open CApp.h and add the following code, in here is where our tutorial will really begin:

#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

class CApp {

    public:

        CApp();

        int OnExecute();

};

#endif
Now, open up CApp.cpp and add the following code:

#include "CApp.h"

CApp::CApp() {
}

int CApp::OnExecute() {
    return 0;
}

int main(int argc, char* argv[]) {
    CApp theApp;

    return theApp.OnExecute();
}
The CApp class is setting the stage for our entire program. Let me step aside to take special note of how games are typically setup. Most games consist of 5 functions that handle how the game processes. These processes are typically:

Initialize
This function handles all the loading of data, whether it be textures, maps, NPCs, or whatever.

Event
This function handles all input events from the mouse, keyboard, joysticks, or other devices.

Loop
This function handles all the data updates, such as a NPCs moving across the screen, decreasing your health bar, or whatever

Render
This function handles all the rendering of anything that shows up on the screen. It does NOT handle data manipulation, as this is what the Loop function is supposed to handle.

Cleanup
This function simply cleans up any resources loaded, and insures a peaceful quitting of the game.

It's important to understand that games are one gigantic loop. Within this loop we find events, update data, and render pictures. Thus, the basic structure could be thought of as:

Initialize();

while(true) {
    Events();
    Loop();
    Render();
}

Cleanup();
Each iteration of the loop we do something with data, and render accordingly. Events are extra, as a means for the user to manipulate data as well. In that sense, Events aren't required to make a game, but are needed when you want the user to be able to manipulate data (such as move a NPC to the left).

Let's clarify this idea with an example. Say we have our Knight, the hero of the game. All we want to do is simply have him move around. If I press left, he goes left. We need to figure out how to do this within a loop. Firstly, we know we want to check for events (keyboard events). Since Events are a means to manipulate data, we also know we'll need some sort of variables to modify. Then we can use these variables to render our Knight appropriatly on the screen. We could have:

if(Key == LEFT) X--;
if(Key == RIGHT) X++;
if(Key == UP) Y--;
if(Key == DOWN) Y++;//... somewhere else in our code ...

RenderImage(KnightImage, X, Y);
This works because each loop it checks if the key is LEFT, RIGHT, etc., and if so, we decrease or increase a variable. So, if our game is running at 30 frames per second and we press LEFT, then our guy would move to the left 30 pixels per second. If you don't understand the game loop, you soon will. Games need them to function correctly.

Going back to our conceptual code (the 5 functions), we can add these additional pages to our project:

CApp_OnInit.cpp CApp_OnEvent.cpp CApp_OnLoop.cpp CApp_OnRender.cpp CApp_OnCleanup.cpp

Go back to CApp.h, and add the following functions and variables:

#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

class CApp {
    private:
        bool    Running;

    public:
        CApp();

        int OnExecute();

    public:

        bool OnInit();

        void OnEvent(SDL_Event* Event);

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
Go through each of the 5 files we just created and create the function themselves:

#include "CApp.h"

bool CApp::OnInit() {
    return true;
}
#include "CApp.h"

void CApp::OnEvent(SDL_Event* Event) {
}
#include "CApp.h"

void CApp::OnLoop() {
}
#include "CApp.h"

void CApp::OnRender() {
}
#include "CApp.h"

void CApp::OnCleanup() {
}

Now lets go back to our CApp.cpp code to link all of these functions together:

#include "CApp.h"

CApp::CApp() {
    Running = true;
}

int CApp::OnExecute() {
    if(OnInit() == false) {
        return -1;
    }

    SDL_Event Event;

    while(Running) {
        while(SDL_PollEvent(&Event)) {
            OnEvent(&Event);
        }

        OnLoop();
        OnRender();
    }

    OnCleanup();

    return 0;
}

int main(int argc, char* argv[]) {
    CApp theApp;

    return theApp.OnExecute();
}
You'll notice some new variables, but let's look at what is happening first. First, we try to Initialize our game, if it fails we return -1 (an error code), thus closing our program. If everything is good, we continue on to the game loop. Within the game loop we use SDL_PollEvent to check for events, and pass them one at a time to OnEvent. Once done with Events, we go to OnLoop for move data around and what not, and then render our game. We repeat this indefinitly. If the user exits the game, we proceed to OnCleanup cleaning up any resources. Simple enough.

Now, lets look at SDL_Event and SDL_PollEvent. The first is a structure that holds information about events. The second is a function that will grab any events waiting in the queue. This queue can have any number of events, which is the reason why we have to loop through them. So, for example, lets say the user presses A and moves the mouse during the OnRender() function. SDL will detect this and put two events in the queue, one for a key press and one for a mouse move. We can grab this event from the queue by using the SDL_PollEvent, and then passing it to OnEvent to handle it accordingly. Once there are no more events in the queue, SDL_PollEvent will return false, thus exiting out of the Event queue loop.

The other variable added, Running, is our own. This is our exit out of the game loop. When this is set to false, it will end the program, and in turn exit the program. So, for example, if the user presses the Escape key we can set this variable to false, quitting the game.

You should be able to compile everything so far just fine, but you may notice you can't exit out. You'll probably have to use the task manager to end the program.

Now that everything is setup, let's start by creating the window our game will be drawn in. Jump over to CApp.h and add a SDL surface variable to the code:

#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

class CApp {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
I suppose now would be a good time to explain exactly what a SDL Surface is. An SDL Surface is anything that can be drawn, or drawn to. Say we have a blank piece of paper, a pencil, and some stickers; this paper could be called our display "surface." We can draw to it, put stickers on it, or whatever. The stickers we have are also surfaces; we can draw on them and put other stickers on them as well. So, Surf_Display is simply our "piece of blank paper" that we will draw all our stuff on.

Now, lets jump on over to CApp_OnInit to actually create this surface:

#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    return true;
}
The first thing we need to do is start up SDL itself, so we can access its functions. We are telling SDL to Initialize everything it has; there are other parameters you can pass, but understanding them at this point is not important. The next function we use is SDL_SetVideoMode. This bad boy is what creates our window, and our surface. It takes 4 parameters: The width of the window, the height of the window, the bit resolution of the window (recommended to be 16 or 32), and then display flags. There are quite a few display flags, but the ones shown above are fine for now. The first flag tells SDL to use hardware memory for storing our images and such, and the second flag tells SDL to use double buffering (which is important if you don't want flickering on your screen). Another flag that may interest you now is SDL_FULLSCREEN, which makes the window go fullscreen.

Now that our display is setup, lets cleanup a little bit to make sure everything works smoothly. Open up CApp_OnCleanup.cpp and add the following:

#include "CApp.h"

void CApp::OnCleanup() {
    SDL_Quit();
}
We basically quit out of SDL. You should take note that in this function is where you would free other surfaces as well. This keeps all your code centralized to the function its performing.

To keep things tidy, lets also set the Surf_Display pointer to NULL on the class constructor. Open up CApp.cpp and add the following:

CApp::CApp() {
    Surf_Display = NULL;

    Running = true;
}
Try compiling your code, and watching it work. You should have a nice blank window popup. You'll notice you can't close it though, so you'll need to use the task manager to do so.

Now that we have a window all setup, we're going to need a way to close it. Open up the CApp_OnEvent.cpp file, add the following:

#include "CApp.h"

void CApp::OnEvent(SDL_Event* Event) {
    if(Event->type == SDL_QUIT) {
        Running = false;
    }
}
The SDL event structure is broken down into types. These types can range from keypresses, to mouse movements; what we simply are doing here is checking the event type. The type above we are looking for is the request to close the window (i.e., when the user clicks the X button). If that event happens to take place, we set Running to false, thus ending our program. Simple enough. We'll look more into events in a later lesson.

Now, you should everything setup, and a good structure to work with later. It may be a good idea to take this project and turn it into an "SDL template" within CodeBlocks. I won't go over how to do that, but feel free to Google it.


SDL Coordinates and Bliting
Using the first tutorial as our base, we'll delve more into the world of SDL surfaces. As I attempted to explain in the last lesson, SDL Surfaces are basically images stored in memory. Imagine we have a blank 320x240 pixel surface. Illustrating the SDL coordinate system, we have something like this:

SDL Coordinate System

This coordinate system is quite different than the normal one you are familiar with. Notice how the Y coordinate increases going down, and the X coordinate increases going right. Understanding the SDL coordinate system is important in order to properly draw images on the screen.

Since we already have the main surface (Surf_Display) setup and ready, we are going to need a way to draw images on it. This process is called Blitting, where we basically transfer one image onto another. But before we can do that, we have to have a way to load these images into memory. SDL offers a simple function to do this called SDL_LoadBMP. Some pseudocode might look like this:
SDL_Surface* Surf_Temp;

if((Surf_Temp = SDL_LoadBMP("mypicture.bmp")) == NULL) {
    //Error!
}
It's rather simple, SDL_LoadBMP takes a single argument, the file you want to load, and returns a surface. If the functions returns NULL, either the file is not found, corrupted, or some other error. Unfortunately, for the sake of effeciency, this method is not enough. Often the image loaded will be in a different pixel format than the display. Thus, when we draw an image to the display we can incurr performance loss, lose image colors, and the like. But, thanks to SDL, it offers a quick solution around this, SDL_DisplayFormat. This function takes a surface already loaded, and returns a new surface using the same format as the display.

Let's take this process and throw it into a reusable class. Use SDL Tutorial 1 as the basis for you code, and add the following two new files: CSurface.h, CSurface.cpp. Open up CSurface.h and add the following:
#ifndef _CSURFACE_H_
#define _CSURFACE_H_
#include <SDL.h>

class CSurface {
    public:
        CSurface();

    public:
        static SDL_Surface* OnLoad(char* File);
};

#endif
We've created a simple static function, OnLoad, that will load a surface for us. Now open CSurface.cpp:
#include "CSurface.h"

CSurface::CSurface() {
}

SDL_Surface* CSurface::OnLoad(char* File) {
    SDL_Surface* Surf_Temp = NULL;
    SDL_Surface* Surf_Return = NULL;

    if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
        return NULL;
    }

    Surf_Return = SDL_DisplayFormat(Surf_Temp);
    SDL_FreeSurface(Surf_Temp);

    return Surf_Return;
}
There are a couple of important things to note here. Firstly, always remember that when you make a pointer to set it to NULL, or 0. Many problems can come along later if you fail to do this. Secondly, notice how SDL_DisplayFormat returns a new Surface, and doesn't overwrite the original. This important to remember because since it creates a new surface, we have to free the old one. Otherwise, we have a surface floating around in memory.

Now that we have a way of loading surfaces into memory, we are also going to need a way to draw them onto other surfaces. Just like SDL offers a function to load images, it also offers a function to draw (blit) images: SDL_BlitSurface. Unfortunately, this function is not as easy to use as the SDL_LoadBMP, but nonetheless, it's simple enough. Open back up CSurface.h and add the following function prototype:
#ifndef _CSURFACE_H_
    #define _CSURFACE_H_

#include <SDL.h>

class CSurface {
    public:
        CSurface();

    public:
        static SDL_Surface* OnLoad(char* File);

        static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
};

#endif
Now, open back up CSurface.cpp, and add the following:
#include "CSurface.h"

CSurface::CSurface() {
}

SDL_Surface* CSurface::OnLoad(char* File) {
    SDL_Surface* Surf_Temp = NULL;
    SDL_Surface* Surf_Return = NULL;

    if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
        return NULL;
    }

    Surf_Return = SDL_DisplayFormat(Surf_Temp);
    SDL_FreeSurface(Surf_Temp);

    return Surf_Return;
}

bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) {
    if(Surf_Dest == NULL || Surf_Src == NULL) {
        return false;
    }

    SDL_Rect DestR;

    DestR.x = X;
    DestR.y = Y;

    SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR);

    return true;
}
First of all, look at the arguments being passed to the OnDraw function. We have two surfaces, and two int variables. The first surface is the destination surface, or the surface we are going to draw on. The second surface is the source surface, or the surface we going to use to draw on another surface. Basically, we are putting Surf_Src on top of Surf_Dest. The X, Y variables is the position on the Surf_Dest where we are drawing this surface to.

The start of the function makes sure we have valid surfaces, if we don't, return false. Next, we find SDL_Rect. This is a SDL structure that basically has four members: x, y, w, h. This, of course, creates the dimensions for a rectangle. We are only worried about where we are drawing to, not the size. So we assign X, Y coordinates to the destination surface. If you are wondering what NULL is within SDL_BlitSurface, it's another parameter for a SDL_Rect. We'll get to this later on in this lesson.

Lastly, we actualy call the function to draw the image, and then return true.

Now, to make sure all of this works, let's create a test surface. Open up CApp.h, and create a new surface, and include the new created CSurface.h:
#ifndef _CAPP_H_
#define _CAPP_H_
#include <SDL.h>
#include "CSurface.h"

class CApp {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

        SDL_Surface*    Surf_Test;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
Also, initialize the surface to NULL in the constructor:
CApp::CApp() {
    Surf_Test = NULL;
    Surf_Display = NULL;

    Running = true;
}
And, remember to cleanup:
#include "CApp.h"

void CApp::OnCleanup() {
    SDL_FreeSurface(Surf_Test);
    SDL_FreeSurface(Surf_Display);
    SDL_Quit();
}
Now, lets actually load an image. Open up CApp_OnInit.cpp, and add the code to load a surface:
#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) {
        return false;
    }

    return true;
}
Be sure to replace "myimage.bmp" with an actual bitmap image you have. If you don't have one, open mspaint and draw something real quick, and save it in the same folder where your executable goes. Now that we have the image loaded, lets actually draw it. Open up CApp_OnRender.cpp and add the following:
#include "CApp.h"

void CApp::OnRender() {
    CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);

    SDL_Flip(Surf_Display);
}
Notice a new function here SDL_Flip. This basically refreshes the buffer and displays Surf_Display onto the screen. This is called double buffering. It's the process of drawing everything into memory, and then finally drawing everything to the screen. If we didn't do this, we would have images flickering on the screen. Remember the SDL_DOUBLEBUF flag? This is what turns double buffering on.

Compile your code, and make sure everything works correctly. You should see your image on the top left hand corner of the screen. If so, congratulations, you are one step closer to an actual game. If not, make sure you have the myimage.bmp in the same folder as your executable. Also insure it's a valid bitmap file.

Draw Surface

Now lets take this process a little bit further. While it's nice and all to draw images to the screen, often we'll need to draw only parts of an image. Take, for example, a tileset:

Tileset

Though this is one single image, we only want to draw a part of it. Open back up CSurface.h, and add the following code:
#ifndef _CSURFACE_H_
    #define _CSURFACE_H_

#include <SDL.h>

class CSurface {
    public:
        CSurface();

    public:
        static SDL_Surface* OnLoad(char* File);

        static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);

        static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H);
};

#endif
Open back up CSurface.cpp, and add the following function (Important, we are adding a second OnDraw function, we are not replacing the first one):
bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H) {
    if(Surf_Dest == NULL || Surf_Src == NULL) {
        return false;
    }

    SDL_Rect DestR;

    DestR.x = X;
    DestR.y = Y;

    SDL_Rect SrcR;

    SrcR.x = X2;
    SrcR.y = Y2;
    SrcR.w = W;
    SrcR.h = H;

    SDL_BlitSurface(Surf_Src, &SrcR, Surf_Dest, &DestR);

    return true;
}
Notice that it's basically the same function as our first one, except we've added another SDL_Rect. This source rect allows use to specify what pixels from the source to copy over to the destination. If we specified 0, 0, 50, 50 as parameters for X2...H, it would only draw upper left part of the surface (a 50x50 square). Draw Surface 2

Lets also test this new function out, open back up CApp_OnRender.cpp, and add the following:
#include "CApp.h"

void CApp::OnRender() {
    CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);
    CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50);

    SDL_Flip(Surf_Display);
}


SDL Events
Alongside the basics of game development is something called Events. All videogames, from pong to the highly complex PC games and console titles, use events to interact with the player. These events can come from keyboards, mice, joysticks, gamepads, and so on, or events from our operating system. It's important to understand how events work if we are to appropriately interact a user with a game. We've already been using events, but only for closing our window, now we'll look at how to receive events from the user.

If you haven't caught on, each tutorial builds off the previous, so we'll be using the code from the previous lesson. In order to track all these events and throw them into nice functions, lets create a new class. Create two files called CEvent.h and CEvent.cpp. These two files will handle our events, and call the appropriate function. CApp will inherit this class, so when we need to catch an event we simply override a function.

Open up CEvent.h and add the following code:
#ifndef _CEVENT_H_
    #define _CEVENT_H_

#include <SDL.h>

class CEvent {
    public:
        CEvent();

        virtual ~CEvent();

        virtual void OnEvent(SDL_Event* Event);

        virtual void OnInputFocus();

        virtual void OnInputBlur();

        virtual void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);

        virtual void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode);

        virtual void OnMouseFocus();

        virtual void OnMouseBlur();

        virtual void OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle);

        virtual void OnMouseWheel(bool Up, bool Down);    //Not implemented

        virtual void OnLButtonDown(int mX, int mY);

        virtual void OnLButtonUp(int mX, int mY);

        virtual void OnRButtonDown(int mX, int mY);

        virtual void OnRButtonUp(int mX, int mY);

        virtual void OnMButtonDown(int mX, int mY);

        virtual void OnMButtonUp(int mX, int mY);

        virtual void OnJoyAxis(Uint8 which, Uint8 axis, Sint16 value);

        virtual void OnJoyButtonDown(Uint8 which, Uint8 button);

        virtual void OnJoyButtonUp(Uint8 which, Uint8 button);

        virtual void OnJoyHat(Uint8 which, Uint8 hat, Uint8 value);

        virtual void OnJoyBall(Uint8 which, Uint8 ball, Sint16 xrel, Sint16 yrel);

        virtual void OnMinimize();

        virtual void OnRestore();

        virtual void OnResize(int w,int h);

        virtual void OnExpose();

        virtual void OnExit();

        virtual void OnUser(Uint8 type, int code, void* data1, void* data2);
};

#endif
Pretty hefty class right? Alright, open up CEvent.cpp, and add the following code:
#include "CEvent.h"

CEvent::CEvent() {
}

CEvent::~CEvent() {
    //Do nothing
}

void CEvent::OnEvent(SDL_Event* Event) {
    switch(Event->type) {
        case SDL_ACTIVEEVENT: {
            switch(Event->active.state) {
                case SDL_APPMOUSEFOCUS: {
                    if ( Event->active.gain )    OnMouseFocus();
                    else                OnMouseBlur();

                    break;
                }
                case SDL_APPINPUTFOCUS: {
                    if ( Event->active.gain )    OnInputFocus();
                    else                OnInputBlur();

                    break;
                }
                case SDL_APPACTIVE:    {
                    if ( Event->active.gain )    OnRestore();
                    else                OnMinimize();

                    break;
                }
            }
            break;
        }

        case SDL_KEYDOWN: {
            OnKeyDown(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
            break;
        }

        case SDL_KEYUP: {
            OnKeyUp(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
            break;
        }

        case SDL_MOUSEMOTION: {
            OnMouseMove(Event->motion.x,Event->motion.y,Event->motion.xrel,Event->motion.yrel,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_LEFT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_RIGHT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_MIDDLE))!=0);
            break;
        }

        case SDL_MOUSEBUTTONDOWN: {
            switch(Event->button.button) {
                case SDL_BUTTON_LEFT: {
                    OnLButtonDown(Event->button.x,Event->button.y);
                    break;
                }
                case SDL_BUTTON_RIGHT: {
                    OnRButtonDown(Event->button.x,Event->button.y);
                    break;
                }
                case SDL_BUTTON_MIDDLE: {
                    OnMButtonDown(Event->button.x,Event->button.y);
                    break;
                }
            }
            break;
        }

        case SDL_MOUSEBUTTONUP:    {
            switch(Event->button.button) {
                case SDL_BUTTON_LEFT: {
                    OnLButtonUp(Event->button.x,Event->button.y);
                    break;
                }
                case SDL_BUTTON_RIGHT: {
                    OnRButtonUp(Event->button.x,Event->button.y);
                    break;
                }
                case SDL_BUTTON_MIDDLE: {
                    OnMButtonUp(Event->button.x,Event->button.y);
                    break;
                }
            }
            break;
        }

        case SDL_JOYAXISMOTION: {
            OnJoyAxis(Event->jaxis.which,Event->jaxis.axis,Event->jaxis.value);
            break;
        }

        case SDL_JOYBALLMOTION: {
            OnJoyBall(Event->jball.which,Event->jball.ball,Event->jball.xrel,Event->jball.yrel);
            break;
        }

        case SDL_JOYHATMOTION: {
            OnJoyHat(Event->jhat.which,Event->jhat.hat,Event->jhat.value);
            break;
        }
        case SDL_JOYBUTTONDOWN: {
            OnJoyButtonDown(Event->jbutton.which,Event->jbutton.button);
            break;
        }

        case SDL_JOYBUTTONUP: {
            OnJoyButtonUp(Event->jbutton.which,Event->jbutton.button);
            break;
        }

        case SDL_QUIT: {
            OnExit();
            break;
        }

        case SDL_SYSWMEVENT: {
            //Ignore
            break;
        }

        case SDL_VIDEORESIZE: {
            OnResize(Event->resize.w,Event->resize.h);
            break;
        }

        case SDL_VIDEOEXPOSE: {
            OnExpose();
            break;
        }

        default: {
            OnUser(Event->user.type,Event->user.code,Event->user.data1,Event->user.data2);
            break;
        }
    }
}

void CEvent::OnInputFocus() {
    //Pure virtual, do nothing
}

void CEvent::OnInputBlur() {
    //Pure virtual, do nothing
}

void CEvent::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    //Pure virtual, do nothing
}

void CEvent::OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode) {
    //Pure virtual, do nothing
}

void CEvent::OnMouseFocus() {
    //Pure virtual, do nothing
}

void CEvent::OnMouseBlur() {
    //Pure virtual, do nothing
}

void CEvent::OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle) {
    //Pure virtual, do nothing
}

void CEvent::OnMouseWheel(bool Up, bool Down) {
    //Pure virtual, do nothing
}

void CEvent::OnLButtonDown(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnLButtonUp(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnRButtonDown(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnRButtonUp(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnMButtonDown(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnMButtonUp(int mX, int mY) {
    //Pure virtual, do nothing
}

void CEvent::OnJoyAxis(Uint8 which,Uint8 axis,Sint16 value) {
    //Pure virtual, do nothing
}

void CEvent::OnJoyButtonDown(Uint8 which,Uint8 button) {
    //Pure virtual, do nothing
}

void CEvent::OnJoyButtonUp(Uint8 which,Uint8 button) {
    //Pure virtual, do nothing
}

void CEvent::OnJoyHat(Uint8 which,Uint8 hat,Uint8 value) {
    //Pure virtual, do nothing
}

void CEvent::OnJoyBall(Uint8 which,Uint8 ball,Sint16 xrel,Sint16 yrel) {
    //Pure virtual, do nothing
}

void CEvent::OnMinimize() {
    //Pure virtual, do nothing
}

void CEvent::OnRestore() {
    //Pure virtual, do nothing
}

void CEvent::OnResize(int w,int h) {
    //Pure virtual, do nothing
}

void CEvent::OnExpose() {
    //Pure virtual, do nothing
}

void CEvent::OnExit() {
    //Pure virtual, do nothing
}

void CEvent::OnUser(Uint8 type, int code, void* data1, void* data2) {
    //Pure virtual, do nothing
}
Lots of code, but all the SDL events should be covered. What we basically are doing is taking an SDL_Event pointer, and switching through the types, and then calling the appropriate function. It just looks like a lot since they are quite a bit of events.

Now that this has been all setup, let's jump over to CApp.h and link the two together:
#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

#include "CEvent.h"
#include "CSurface.h"

class CApp : public CEvent {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

        SDL_Surface*    Surf_Test;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
Everything should compile just fine. We have the event class setup, now lets actually link events to our new class structure. Open up CApp_OnEvent.cpp and edit the following function:
#include "CApp.h"

void CApp::OnEvent(SDL_Event* Event) {
    CEvent::OnEvent(Event);
}
We're passing the Event structure to our class now, it will take care of the correct function calls. Now when we want to check for an event, we override the function. Since we got rid of checking for the SDL_QUIT event, lets use the function instead. Open up CApp.h again, and add the following function:
#ifndef _CAPP_H_
#define _CAPP_H_
#include <SDL.h>
#include "CEvent.h"
#include "CSurface.h"

class CApp : public CEvent {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

        SDL_Surface*    Surf_Test;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

        void OnExit();

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
The OnExit function handles the SDL_QUIT events. Now that we have the prototype, lets define what it does. Open up CApp_OnEvent.cpp, and add the following:
#include "CApp.h"

void CApp::OnEvent(SDL_Event* Event) {
    CEvent::OnEvent(Event);
}

void CApp::OnExit() {
    Running = false;
}
Recompile, and try it out. You should be able to close the application just like before.

I encourage you to look through some of the various events that we can check. Later on we'll use some of these events in our games, but for now try to familiarize yourself with them.


SDL Animation
The last tutorial we took our chance at making a Tic-Tac-Toe game. Hopefully most of you were successful in getting it to work. If not, don't fret, you'll get the hang of all of this eventually.

In this tutorial we are going to take our hand at SDL Animation. As before, we'll be building on top of the previous SDL lessons (but not including the Tic Tac Toe one). So lets get started.

We'll be creating a new class to handle Animation, and in the next tutorial we will create a class to handle Entities. Please keep in mind that these two things are seperate, and while I know they could be one class, I don't wish to take that approach. So please hold back your criticism.

Create two new files (I am sure you are seeing a pattern now, hopefully), called CAnimation.h and CAnimation.cpp. Eventually, our CEntity class will inherit this class, but for now we'll be testing through an object we create later. And before we get started add the include directive within CApp.h (before #include "CEvent.h" is just fine).
#include "CAnimation.h"
Now open up CAnimation.h and lets start coding. Add the following basic class structure to your file.
#ifndef _CANIMATION_H_
    #define _CANIMATION_H_

#include <SDL.h>

class CAnimation {
    private:
        int    CurrentFrame;

        int     FrameInc;

    private:
        int     FrameRate; //Milliseconds

        long    OldTime;

    public:
        int    MaxFrames;

        bool    Oscillate;

    public:
        CAnimation();

        void OnAnimate();

    public:
        void SetFrameRate(int Rate);

        void SetCurrentFrame(int Frame);

        int GetCurrentFrame();
};

#endif
Now, open up CAnimation.cpp and add this code:
#include "CAnimation.h"

CAnimation::CAnimation() {
    CurrentFrame    = 0;
    MaxFrames       = 0;
    FrameInc        = 1;

    FrameRate       = 100; //Milliseconds
    OldTime         = 0;

    Oscillate       = false;
}

void CAnimation::OnAnimate() {
    if(OldTime + FrameRate > SDL_GetTicks()) {
        return;
    }

    OldTime = SDL_GetTicks();

    CurrentFrame += FrameInc;

    if(Oscillate) {
        if(FrameInc > 0) {
            if(CurrentFrame >= MaxFrames) {
                FrameInc = -FrameInc;
            }
        }else{
            if(CurrentFrame <= 0) {
                FrameInc = -FrameInc;
            }
        }
    }else{
        if(CurrentFrame >= MaxFrames) {
            CurrentFrame = 0;
        }
    }
}

void CAnimation::SetFrameRate(int Rate) {
    FrameRate = Rate;
}

void CAnimation::SetCurrentFrame(int Frame) {
    if(Frame < 0 || Frame >= MaxFrames) return;

    CurrentFrame = Frame;
}

int CAnimation::GetCurrentFrame() {
    return CurrentFrame;
}
Some explanation on what this class is all about now. There is one basic element of animation that we need to handle, that is the current frame of the animation. Take the Yoshi image below for example (we'll be using him in this tutorial). You'll notice we have 8 frames of Yoshi on one image. Each frame would then be labeled 0, 1, 2 from Top to Bottom.

Remember the second tutorial where we create a function to draw part of an image? Well, if we take that function in conjunction with the frame of the animation, voila!

So the first variable I want you to see is CurrentFrame. This is the current frame of the animation that we are going to draw on the screen. Whatever value it has, will determine what part of the surface we will draw to the screen. So, when we call our draw function, we would do something like this:
CSurface::OnDraw(Surf_Display, Surf_Image, 0, 0, 0, Anim_Yoshi.GetCurrentFrame() * 64, 64, 64);
Since our Yoshi is really 64 x 64 pixels, that's the width and height we will grab, and is also how we grab the frame. Look at the image below for an illustration.

When the CurrentFrame increases by 1, we jump down 64 pixels (the height of Yoshi's frame), and draw that frame.

The other part of this class is that we need to know how many frames this animation is, thus the MaxFrames. The last vital part to know is how many frames per second, or rather, how fast this animation is going to display. In order to determine that we find use this bit of code found in the OnAnimate function.
if(OldTime + FrameRate > SDL_GetTicks()) {
    return;
}
By taking the Old Time in milliseconds plus the desired frame rate, we can check it against how long the SDL has been running. So, for example, we just started our program. SDL_GetTicks is 0, and OldTime is 0. Our desired frame rate is 1 frame per second. So FrameRate = 1000 (milliseconds). So, is 0 + 1000 greater than 0? Yes, thus we will skip over the function and wait. But once 0 + 1000 is less than SDL_GetTicks, that must mean 1 second has passed. So we increment the frame, and then reset OldTime to the current time, and start over.

The next interesting tid bit is Oscillate and FrameInc. Not that I wanted to confuse anyone by adding this, but I feel it's necessary for certain things later on. Basically, when the Oscillate flag is true, the animation will increase frames, and then decrease frames. If we had an animation with 10 frames, it would do something like this:

0 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 2 ...

You see, it goes up to 9, and then decreases back down to 0, and so on. There are some interesting uses for this, but we'll get into that in other lessons. So how does this work? Take a look at the OnAnimate function.
void CAnimation::OnAnimate() {
    if(OldTime + FrameRate > SDL_GetTicks()) {
        return;
    }

    OldTime = SDL_GetTicks();

    CurrentFrame += FrameInc;

    if(Oscillate) {
        if(FrameInc > 0) {
            if(CurrentFrame >= MaxFrames) {
                FrameInc = -FrameInc;
            }
        }else{
            if(CurrentFrame <= 0) {
                FrameInc = -FrameInc;
            }
        }
    }else{
        if(CurrentFrame >= MaxFrames) {
            CurrentFrame = 0;
        }
    }
}

Bug Fix: if(CurrentFrame >= MaxFrames - 1) needs to be if(CurrentFrame >= MaxFrames). Kudos to everyone in the comments mentioning this.

Bug Fix: There was an error with placing CurrentFrame += FrameInc; after the if statement, it should have been placed before the if statement. What would happen is if the animation had MaxFrames = 0, it would still increase the CurrentFrame to 1. Thanks Alexander Mangel for helping find this bug!

We already know what the OldTime and such do, but what about the rest? For now, look at the else statement of the Oscillate if statement. You'll notice we're simply checking if the CurrentFrame has exceeded the Max Frames. If it has, reset back to 0. Pretty simple. Then below that, outside of the block, we increase to the next frame.

Now, the more confusing part is the Oscillate if statement. This is where the FrameInc variable comes in. Basically, the FrameInc is set to 1 or -1, depending on how we are increasing or decreasing the frames. Remember, Oscillating causes the frames to go from 0 to 9 back to 0. So if the FrameInc is greater than 0, we are increasing the frames, otherwise we are decreasing frames. The innermost if statements basically inverse the FrameInc if we reached the end of 0, or MaxFrames.

Now that that is all taken care of, lets put this class into action. Create a new CAnimation object within CApp.h:
CAnimation      Anim_Yoshi;
Now, lets set the MaxFrames, add this to CApp_OnInit:
Anim_Yoshi.MaxFrames = 8;
If you want to see your animation Oscillate, set this:
Anim_Yoshi.Oscillate = true;
Okay, now to make our animation loop, add this to CApp_OnLoop:
Anim_Yoshi.OnAnimate();
Now, the last thing, to make it actually animate; add this to CApp_OnRender:
CSurface::OnDraw(Surf_Display, Surf_Test, 290, 220, 0, Anim_Yoshi.GetCurrentFrame() * 64, 64, 64);
Now try compiling, and watch your Yoshi workout! Make sure you replace the myimage.bmp with the Yoshi image.


SDL Entities
In this new tutorial, as I had promised before, we are going to take our hand at creating entities. Entities, for all gaming purposes, are anything that can be interacted with in any way, shape, or form. Some examples might be a monster or a treasure chest that you can open. In this sense, practically everything within the game that moves is an Entity. A rock that is part of a map, which never moves, is not an entity. But if you wanted that rock to move for whatever reason, then we'd make it an Entity. This tutorial will be split into 3 different tutorials. The first, this one you are reading, will deal with a basic Entity class structure. The next tutorial will veer off slightly to build a Map class via a tileset. Then, the last tutorial, which is what a lot of people have trouble with, will deal with Entity to Map collision, and Entity to Entity Collision.

Update:
- Fixed class below to have a virtual destructor. (Thanks Andras!)

Lets get started by creating two new files called CEntity.cpp and CEntity.h. Open up the header file and add the following:
#include <vector>
#include "CAnimation.h"
#include "CSurface.h"

class CEntity {
    public:
        static std::vector<CEntity*>    EntityList;

    protected:
        CAnimation      Anim_Control;

        SDL_Surface*    Surf_Entity;

    public:
        float           X;
        float           Y;

        int             Width;
        int             Height;

        int             AnimState;

    public:
        CEntity();

        virtual ~CEntity();

    public:
        virtual bool OnLoad(char* File, int Width, int Height, int MaxFrames);

        virtual void OnLoop();

        virtual void OnRender(SDL_Surface* Surf_Display);

        virtual void OnCleanup();
};
Now, open up the cpp file and add the following:
#include "CEntity.h"

std::vector<CEntity*> CEntity::EntityList;

CEntity::CEntity() {
    Surf_Entity = NULL;

    X = Y = 0.0f;

    Width = Height = 0;

    AnimState = 0;
}

CEntity::~CEntity() {
}

bool CEntity::OnLoad(char* File, int Width, int Height, int MaxFrames) {
    if((Surf_Entity = CSurface::OnLoad(File)) == NULL) {
        return false;
    }

    CSurface::Transparent(Surf_Entity, 255, 0, 255);

    this->Width = Width;
    this->Height = Height;

    Anim_Control.MaxFrames = MaxFrames;

    return true;
}

void CEntity::OnLoop() {
    Anim_Control.OnAnimate();
}

void CEntity::OnRender(SDL_Surface* Surf_Display) {
    if(Surf_Entity == NULL || Surf_Display == NULL) return;

    CSurface::OnDraw(Surf_Display, Surf_Entity, X, Y, AnimState * Width, Anim_Control.GetCurrentFrame() * Height, Width, Height);
}

void CEntity::OnCleanup() {
    if(Surf_Entity) {
        SDL_FreeSurface(Surf_Entity);
    }

    Surf_Entity = NULL;
}
Okay, now for some basic explanation. What we are doing here is encapsulating the basic 5 components I mentioned within the first lesson (excluding Events, which will be handled in a later lesson). This allows us to handle Entities within the game much more easily, rather than clumping them together with everything else in the game within the main CApp class. This will also be the way we handle other things as well. The first thing you may notice is a static vector called EntityList. This vector will hold all of our entities, easily accessible through CEntity::EntityList, because it's declared as a static. I should make a special note here: we declare this EntityList within CEntity because it prevents from circular dependencies later on. An example of this is trying to get a Map to communicate with Entities, and Entities to get to communicate with the Map. Such as CMap declaring a CEntity member, and CEntity declaring a CMap member. It would cause problems on the compile level.

So this vector contains all of our Entities within the game. Notice that each member of the vector is a pointer. This is because later on we are going to inherit this CEntity class for Entity specific classes. So, for example, if we were going to make a Megaman game, we would have a CMegaMan class inheriting the CEntity class. And, via polymorphism, we can store that CMegaMan class within the EntityList. This is the very reason why we declared the functions above as virtuals, and certain members as protected.

Next, we have basic information about the Entity, common to all Entities, coordinates, dimensions, and a surface for its image. Next, we have a loading function that basically takes a filename, and loads the image. By default, we have it setting a transparent color. I'd like to step aside here for a moment to let you all know that certain things I do aren't set in stone. You can, and are encourage, to take this code and modify to your liking. You may want more parameters on your OnLoad function, or you may want less. You may not want a default transparent color, who knows. I encourage you to test different things. Don't worry, my code will still be here if you mess things up.

Next, we have a basic OnLoop function that handles basic calculations. Right now we are only calculating Animation. Also please note that we have only set the MaxFrames for the Animation, and left the defaults in place. Next, we have the OnRender function. Instead of making it render to the display only, I've allowed a parameter to specify where to render this entity. This could be any surface you want. So you could, if you wanted, render one entity onto another.

Lastly, we have an OnCleanup function that restores memory and all that stuff.

Like I mentioned in the beginning, this is a basic Entity class structure, it basically doesn't do much yet, but don't fret, it soon will in coming lessons. So lets get it working. Open up CApp.h and add the header file to the top, and declare two Entities:
#include "CEntity.h"

//...

private:
    CEntity         Entity1;
    CEntity         Entity2;
Now, lets load these two Entities. Open up CApp_OnInit.cpp and add the following:
if(Entity1.OnLoad("./entity1.bmp", 64, 64, 8) == false) {
    return false;
}

if(Entity2.OnLoad("./entity2.bmp", 64, 64, 8) == false) {
    return false;
}

Entity2.X = 100;

CEntity::EntityList.push_back(&Entity1);
CEntity::EntityList.push_back(&Entity2);
Now, depending on the images you use, you need to set the values appropriately on the OnLoad function. I've reused the yoshi image from the previous lesson, and if you still need it:

Now, remember how I stated we are basically encapsulating the basic functions of a game within the Entity class? We have to call those functions now in the respective CApp functions. So, open up CApp_OnLoop.cpp and add the following:
for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnLoop();
}
We are basically running through each Entity in our vector, and calling the OnLoop function. Simple enough! (And we're doing an error checking so we don't call any NULL pointers). Now, lets do the same things in CApp_OnRender.cpp:
for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnRender(Surf_Display);
}
And the same thing in CApp_OnCleanup.cpp:
for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnCleanup();
}

CEntity::EntityList.clear();

Note that I added the clear function call, which clears out the vector to nothing. Basically a reset.


SDL Maps
As I stated in the last lesson, we're going to be looking at making a Map class that will be tile based. In addition to the maps, we'll be creating Areas that enhouse many Maps. While we could create one giant map, it's far easier to manage many smaller maps, and also opens the possibility of tiling maps as well. By the way, head on over to the SDL Image tutorial if you haven't already done so, we will be making the switch over to SDL Image and stop using SDL_LoadBMP. No more chatter, lets get started.

Known Bug: The camera settings for this tutorial are backwards, and are reversed in the SDL Collision tutorial (and following). Changes are not put in place yet, to get the fixes view the SDL Collision tutorial.

We're going to need several files in the beginning, Define.h, CArea.h, CArea.cpp, CMap.h, CMap.cpp, CTile.h, and CTile.cpp. So create these blank files. What we are going to do is have one Area object, this Area object will have many children Map objects in a vector (like the Entities in a vector). Within each of these Map objects, they will have a vector of each individual tile. So basically, we have a bunch of tiles that make up a map, and a bunch of maps make up an area.

Lets start by opening up Define.h. This file is going to hold some constant values, as defines, that will define things like map width and height, screen width and height, and tile size. These are going to be some values that we will be using throughout making our Maps and such. Add the following below to define.h.
#ifndef _DEFINE_H_
    #define _DEFINE_H_

#define MAP_WIDTH    40
#define MAP_HEIGHT    40

#define TILE_SIZE    16

#define WWIDTH        640
#define    WHEIGHT        480

#endif
We'll get back to what these values mean later, but WWIDTH and WHEIGHT are the values set in SDL_SetVideoMode. So open up CApp_Init and change that function call to the following:
if((Surf_Display = SDL_SetVideoMode(WWIDTH, WHEIGHT, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
    return false;
}
Simple enough. Next, open up CTile.h. This class is going to define a single tile on a map. In gaming terms, a tile is basically a single square graphic that we draw to the screen. Remember the animation tutorial, each frame of Yoshi could be thought of as a tile. So when I say we have a map made up of many tiles, we are taking these tiles and repeating them in a grid sequence.

And since we have a single graphic containg all of our tiles, we only have to load one graphic, and not several for each tile. Each tile then needs some properties. The most obvious is which graphical tile to use. Take a look at an example tileset below:

You'll notice that the very top left has an ID of 0, and going to the left it increments in 1s. This is called the Tile ID. By using the ID, and the size of each tile (TILE_SIZE), we can know which tile to draw to the screen. The next important thing is TypeID. This determines what type of tile it is. Some examples might be, invisible (don't draw a tile), block (like a wall), or ground (player can walk on it). You can define whatever TypeIDs you want, but these are the most basic (other examples might be water, fire, ice, etc...). That's basically it, this Tile class only has two members (for now, later on it will contain information for animated tiles). So lets push this class together inside of CTile.h:
#ifndef _CTILE_H_
#define _CTILE_H_
#include "Define.h"

enum {
    TILE_TYPE_NONE = 0,

    TILE_TYPE_NORMAL,
    TILE_TYPE_BLOCK
};

class CTile {
    public:
        int     TileID;
        int     TypeID;

    public:
        CTile();
};

#endif
Notice the enum used. This lets us assign or check the TypeID, and know what it is at the same time. So if I did TypeID == TILE_TYPE_BLOCK, I would be able to notice in my code that I am checking for a block type of tile. TypeID == 2 isn't as easy to interpret. If you never used enums, think of them like const variables. The value never changes. Also, the starting value defines the rest, notice I have = 0 on TILE_TYPE_NONE. From there the rest of the variables would automatically be assigned values in increments of 1.

Now, lets open up CTile.cpp and finish the code:
#include "CTile.h"

CTile::CTile() {
    TileID = 0;
    TypeID = TILE_TYPE_NONE;
}
Pretty straight forward. Next, lets look at making some maps. The first thing we need to consider is that our maps will be text files. So we need to come up with a file format, that way we can easily edit our maps outside of our code. And possibly later you could create a map editor.

Each map is going to have a width and height, defining the number of tiles. So a map of 10x10, would have 100 tiles in it. We are going to make all of our maps the same width and height, so we don't have to define that within the map files (MAP_WIDTH and MAP_HEIGHT). The part that we do need to add though is the TileID and TypeID for each individual tile. We are going to use the file format that I came up with, as it is relatively simple. Take a look at the 5x5 map example below:

0:0 0:0 0:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0

Each tile within the file would consist of 0:0, effectively being the TileID:TypeID. A space would be the deliminator between the tiles. A concept rendering if this map may look like this:

For this tutorial we will be using 40x40 maps, since that is a pretty decent size. And instead of printing an example out for you to copy, click on this link here:

Example Map First create a folder in the same directory as your exe called maps. This folder is where all your maps will be. Save that map as 1.map. Also, make another folder called tilesets in the same directory as your exe, and save the tileset above as 1.png.

Now that we have a file format for our maps, lets start designing the class that will render this thing. Open up CMap.h:
#ifndef _CMAP_H_
#define _CMAP_H_
#include <SDL.h>
#include <vector>
#include "CTile.h"
#include "CSurface.h"

class CMap {
    public:
        SDL_Surface* Surf_Tileset;

    private:
        std::vector<CTile> TileList;

    public:
        CMap();

    public:
        bool OnLoad(char* File);

        void OnRender(SDL_Surface* Surf_Display, int MapX, int MapY);
};

#endif
A few of the basic things are here, OnLoad and OnRender. OnLoad does what you expect, loads a map from a file, and populates the TileList. The OnRender also does what you expect, properly puts each tile on the screen and draws them using the Surf_Tileset. Lets define what these functions do, now open up CMap.cpp:
#include "CMap.h"

CMap::CMap() {
    Surf_Tileset = NULL;
}

bool CMap::OnLoad(char* File) {
    TileList.clear();

    FILE* FileHandle = fopen(File, "r");

    if(FileHandle == NULL) {
        return false;
    }

    for(int Y = 0;Y < MAP_HEIGHT;Y++) {
        for(int X = 0;X < MAP_WIDTH;X++) {
            CTile tempTile;

            fscanf(FileHandle, "%d:%d ", &tempTile.TileID, &tempTile.TypeID);

            TileList.push_back(tempTile);
        }
        fscanf(FileHandle, "\n");
    }

    fclose(FileHandle);

    return true;
}

void CMap::OnRender(SDL_Surface* Surf_Display, int MapX, int MapY) {
    if(Surf_Tileset == NULL) return;

    int TilesetWidth  = Surf_Tileset->w / TILE_SIZE;
    int TilesetHeight = Surf_Tileset->h / TILE_SIZE;

    int ID = 0;

    for(int Y = 0;Y < MAP_HEIGHT;Y++) {
        for(int X = 0;X < MAP_WIDTH;X++) {
            if(TileList[ID].TypeID == TILE_TYPE_NONE) {
                ID++;
                continue;
            }

            int tX = MapX + (X * TILE_SIZE);
            int tY = MapY + (Y * TILE_SIZE);

            int TilesetX = (TileList[ID].TileID % TilesetWidth) * TILE_SIZE;
            int TilesetY = (TileList[ID].TileID / TilesetWidth) * TILE_SIZE;

            CSurface::OnDraw(Surf_Display, Surf_Tileset, tX, tY, TilesetX, TilesetY, TILE_SIZE, TILE_SIZE);

            ID++;
        }
    }
}

Bug Fix: There was a bug in the continue within the loop, we should be incrementing the ID before continuing. Thanks Zakalwe for helping find this bug!
Bug Fix: We should been dividing by TilesetWidth for TilesetY, otherwise it will not correctly grab the correct Y coordinate unless the Tileset is square. Thanks Anime for helping find this bug!

Lets start at the top. The constructor sets the Tileset to NULL, obviously. Next we have the OnLoad function. First, we are clearing out any old tiles, that way if we load twice, it won't have double the amount of tiles, effectively loading a new map. We then open up a FileHandle, and try to open the requested map file. Now, we go through the map file and grab each tile. This is accomplished by using the two loops. The outer loop is the Y axis loop, going from the top row to the bottom row of the map file. The inner loop is the X axis loop, going from the left most tile, to the right most tile. Pay attention to how this loop works, because it is also used below in the OnRender function. It basically goes from the top left tile, to the bottom right tile, 1 tile at a time. Inside of those loops we create a temporary tile, and load the file information into it. We then push that into our TileList, effectively saving the tile. We close the filehandle, and we are done.

Next, we have the function to render a map. Notice the MapX and MapY arguments. These tell use where to render this map on the screen. This is useful later on for moving maps around. First in the function we check for a valid tileset, because we are going to access this tileset directly and don't want to cause a crash. We then grab the TilesetWidth and TilesetHeight in Tiles. This is important, because we need to know how many tiles a tileset contains, not its actual width and height. That way we can match up a TileID to a tileset. So, a Tileset with a Width and Height, in tiles, of 2x2, would have 4 tiles on it, but in reality it would be 32x32 pixels. So, a TileID of 0 would match the first tile, a TileID of 1 would be the next, and so on. TileIDs repeat on the next row, from left to right.

Okay, next we have that same loop again, going through each tile (Please note that these X and Y coordinates here in the loop are also in number of tiles, they aren't actual pixels). This time, however, we have an ID variable. This ID is incremented by one each loop, and will allow us to grab each tile in the map. So, first we check to see if this Tile should even be drawn, by checking the TypeID. If it is TILE_TYPE_NONE, we skip over it with continue. After that we figure out where to draw this tile on the screen. This is done by taking the MapX and MapY (effectively offset coordinates, because they offset a map from 0, 0 to somewhere, which makes it seem like a map is moving), and taking the X and Y coordinates in the loop. We have to convert these X and Y coordinates back to pixel coordinates, which is done by multiplying it by the size of a tile.

After that, we do something with the Tileset. What we are doing is calculating where on the Tileset to grab the appropriate tile. This is by grabing the TileID of the tile first, and then converting that to a Tile coordinate. A little bit of explanation here. We have our 2x2 tileset, and a TileID of 1. Figuring out X, we would get 1 % 2, which would be 1 still. We when multiply that by the TILE_SIZE, and get 16. That is the correct X coordinate for the Tile. Same with the Y, we put 1 / 2, which is 0.5. Since this an integer operation, the .5 is automatically dropped. Thus we are left with 0. Which is also the correct row. Now, say we had a TileID of 2. 2 % 2 = 0, and 2 / 2 = 1. See how the X repeats in a pattern? 0, 1, 0, 1... And notice how Y increases every time it goes past the Tileset Width? 0, 0, 1, 1. I hope this is clear, as it is somewhat hard to explain.

Next, we actually draw the tile to the screen using the coordinates we just calculated, and then increase the ID to go to the next tile. A little side note here, we could, for the sake of speed, create an OnRender_Cache function that would perform this same operation, but would draw to a Surface defined in the CMap class. Something like SDL_Surface* Surf_Map. Then, the OnRender function would render the Surf_Map only, and not perform any operations. But also take note that that method does not necessarely work later on when we want to animate tiles.

Okay, cool! I know that is quite a bit to take in at once, but don't fret! It's really not too bad once you get the hang of it. I want to say that right now we could create Map objects and render maps just fine. This would be ideal for games like Mario and Megaman, as they are just one big map. But for games like Zelda and Metroid, it won't work too well. That is where Areas come in. Just like maps having file formats, each area will have its own file and file format. Each area is going to have a tileset to load, the size of the area, and the maps that are to be loaded in each area. Here is the area we are going to be using below:

./tilesets/1.png
3
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map

Save this into a file called 1.area, and save it in your maps directory. Just like maps, this area will tile maps. I won't get into detail because I think you all have the point by now.

Open up CArea.h and lets begin:
#ifndef _CAREA_H_
#define _CAREA_H_
#include "CMap.h"

class CArea {
    public:
        static CArea            AreaControl;

    public:
        std::vector<CMap>       MapList;

    private:
        int                     AreaSize;

        SDL_Surface*        Surf_Tileset;

    public:
        CArea();

        bool    OnLoad(char* File);

        void    OnRender(SDL_Surface* Surf_Display, int CameraX, int CameraY);

        void    OnCleanup();
};

#endif
This class is going to similar to the Map class in some ways, but different in others. Similarly, we have MapList that will hold our maps, just like the Map class had a list of tiles. Also, as have a Load function and Render function. Now the differences, we have a static AreaControl, much like Entities have a static control. This will allow us to manipulate an Area from any class by using this object. Next, we have the AreaSize which is the number of maps. We are going to assume that areas will always be square, so an AreaSize of 3, would be a 3x3 area. If you wanted to, and I don't think it's necessary, you would have AreaWidth and AreaHeight. Next, we have a surface for the Tileset. You probably noticed that within the CMap class we never actually loaded a tileset. That's because the Area is going to do it for us, and then pass that pointer to the CMap class. That way each map doesn't load a tileset for itself, but effectively all maps share the same tileset. Again, if you wanted each Map to have its own tileset you can easily modify the class to do so.

Okay, now open up the CArea.cpp class:
#include "CArea.h"

CArea CArea::AreaControl;

CArea::CArea() {
    AreaSize = 0;
}

bool CArea::OnLoad(char* File) {
    MapList.clear();

    FILE* FileHandle = fopen(File, "r");

    if(FileHandle == NULL) {
        return false;
    }

    char TilesetFile[255];

    fscanf(FileHandle, "%s\n", TilesetFile);

    if((Surf_Tileset = CSurface::OnLoad(TilesetFile)) == false) {
        fclose(FileHandle);

        return false;
    }

    fscanf(FileHandle, "%d\n", &AreaSize);

    for(int X = 0;X < AreaSize;X++) {
        for(int Y = 0;Y < AreaSize;Y++) {
            char MapFile[255];

            fscanf(FileHandle, "%s ", MapFile);

            CMap tempMap;
            if(tempMap.OnLoad(MapFile) == false) {
                fclose(FileHandle);

                return false;
            }

            tempMap.Surf_Tileset = Surf_Tileset;

            MapList.push_back(tempMap);
        }
        fscanf(FileHandle, "\n");
    }

    fclose(FileHandle);

    return true;
}

void CArea::OnRender(SDL_Surface* Surf_Display, int CameraX, int CameraY) {
    int MapWidth  = MAP_WIDTH * TILE_SIZE;
    int MapHeight = MAP_HEIGHT * TILE_SIZE;

    int FirstID = -CameraX / MapWidth;
        FirstID = FirstID + ((-CameraY / MapHeight) * AreaSize);

    for(int i = 0;i < 4;i++) {
        int ID = FirstID + ((i / 2) * AreaSize) + (i % 2);

        if(ID < 0 || ID >= MapList.size()) continue;

        int X = ((ID % AreaSize) * MapWidth) + CameraX;
        int Y = ((ID / AreaSize) * MapHeight) + CameraY;

        MapList[ID].OnRender(Surf_Display, X, Y);
    }
}

void CArea::OnCleanup() {
    if(Surf_Tileset) {
        SDL_FreeSurface(Surf_Tileset);
    }

    MapList.clear();
}
Quickly, going from the top, we declare the static object, and then set the AreaSize to 0. Then we have our Load function. It works just like the Load function within CMap, except there are a few differences. We have to load tileset first. We try to load this tileset into the surface, and if it fails return false. We then load the size of the Area. Okay, after that we have two loops, just like maps, that will go through and grab each map filename. It will then create a map object, load the map, set the tileset to be used, and then push it into the list. Pretty simply and straight forward.

Next, we have the Render function. We calculate the actual Map width and height, in pixels, first. This will let us find the first map to render. Some explanation first of what I am trying to do. Since an area can be of any size, like 100x100 maps, we don't want to go through all the trouble and render every single map. We only want to render the maps that are visible in the screen area. For our size of a screen, 640x480, only a possible 4 maps can be visible at one time (like standing at the corner of 4 maps). So what we have to do is calculate the first Map ID to grab. This is going to the first map to render. From the first ID, we know the next 3 maps to render. The one to the right of the first map, the one at the bottom of the first map, and the one at the bottom right of the first map. How is that accomplished? Look at the image below:

We have a guy, the circle, in the very center of the screen, represented by the red square. The other squares are each Map, and we have a Camera position of -700, -700. Why negative? Think about it, the screen itself never really moves, everything else does. So, for something to move up, it must increase its Y in the negative direction. Same with the X coordinate. So, to get to the 4th map, we have to move the area in the negative direction. Now, notice the grayed out maps, these are maps not visible within the players view, so they shouldn't be rendered. To figure out the First ID, which is 4 in this case, we need to use the specified Camera coordinates. We translate those Camera coordinates into Map coordinates. So, taking -(-700) / 640 (which is 40 * 16, remember the MapWidth calculation done above) we get 1 (dropping the decimal because its an integer operation). This is the X coordinate in Maps, but we aren't done. We then calculate the Y coordinate in Maps the same way -(-700) / 640, but we multiply that by the AreaSize. That's because we are grabbing the ID. So, it would become 1 * 3, which is 3, and adding that to the first calculation of 1, we get 4. And what do you know, the First ID on the map!

Okay, so we go through each of the four maps now. Since I did a regular loop of i < 4, we need to figure out how to add that to the First ID, to actually figure out each of the four Map IDs. This is done by taking the First ID as an offset first. We then take i, our position in the loop, divide by two and multiply by the area size. What does this do? Much like the Map class, it creates a pattern, 0, 0, 1, 1. Same with the last operation i % 2, it creates a pattern of 0, 1, 0, 1. This then gives us the correct pattern of, 4, 5, 7, 8. Which are the correct map to render.

We do a little check to make sure the ID is good, since the ID may not exist. And now the last calculation (yes, I know, a lot of complex calculations). It works just like the way we calculated which tile to grab on the tileset. It's turning an ID into actual pixel coordinates, and then offsetting those coordinates by the camera (making it seem like it moved). We then finally render the map, passing the coordinates of where to draw it.

Lastly, we have our cleanup function that frees the surface and clears the maps.

What a load! I hope all of the various calculations don't get you confused. There are really only two basic things we are trying to do, turn pixel coordinates into grid based coordinates. Such as with tiles, and with the maps. The other thing was turning those grid based coordinates back and forth into IDs.

Okay, we are not quite done yet, still a little bit to do (now you see why this tutorial took so long to complete!). You may have noticed that I called the arguments on the Render function on the CArea class CameraX and CameraY. We are going to need to make a camera class that defines the viewing area. This is what we are going to manipulate to move around on a map!

So create two new files, CCamera.cpp and CCamera.h. Open up the header file first:
#ifndef _CCAMERA_H_
#define _CCAMERA_H_
#include <SDL.h>
#include "Define.h"

enum {
    TARGET_MODE_NORMAL = 0,
    TARGET_MODE_CENTER
};

class CCamera {
    public:
        static CCamera CameraControl;

    private:
        int X;
        int Y;

        int* TargetX;
        int* TargetY;

    public:
        int TargetMode;

    public:
        CCamera();

    public:
        void OnMove(int MoveX, int MoveY);

    public:
        int GetX();
        int GetY();

    public:
        void SetPos(int X, int Y);

        void SetTarget(int* X, int* Y);
};

#endif
First, we have a control member object, just like area. Then we have the coordinates of where the camera is. An extra thing I threw in, was the ability to target something. For example, in a Megaman game the camera would target Megaman himself. That way, when Megaman moves the camera would automatically update. So we have two pointers for X and Y coordinates. If these are null, the camera will automatically revert to the cameras position. Next, we have a target mode, which, for now, is either normal (the camera will position to the top left of the target) or center (will center the camera to the target). Pretty simply I think.

We then have a few functions, the first is the OnMove, which will increase the X and Y of the camera by MoveX and MoveY. So OnMove(-1, 0) would move the camera to the left one pixel. We then have Get functions for the coordinates, and the ability to set the coordinates and set a target.

Okay, now open up CCamera.cpp:
#include "CCamera.h"

CCamera CCamera::CameraControl;

CCamera::CCamera() {
    X = Y = 0;

    TargetX = TargetY = NULL;

    TargetMode = TARGET_MODE_NORMAL;
}

void CCamera::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}

int CCamera::GetX() {
    if(TargetX != NULL) {
        if(TargetMode == TARGET_MODE_CENTER) {
            return *TargetX - (WWIDTH / 2);
        }

        return *TargetX;
    }

    return X;
}

int CCamera::GetY() {
    if(TargetY != NULL) {
        if(TargetMode == TARGET_MODE_CENTER) {
            return *TargetY - (WHEIGHT / 2);
        }

        return *TargetY;
    }

    return Y;
}

void CCamera::SetPos(int X, int Y) {
    this->X = X;
    this->Y = Y;
}

void CCamera::SetTarget(int* X, int* Y) {
    TargetX = X;
    TargetY = Y;
}
From top to bottom, like usual, we have are static member first. We then have the constructor defaulting some variables. The OnMove function will increase the X and Y, like I said before. We then have GetX. It will check to see if we have a valid target, and if so return the targets coordinates as the camera coordinates, otherwise return the camera x. For centering mode, we take the screen width and divide by two to find the center of the screen, and then subtract it from the target coordinates. This will center the camera to the target. We then have the SetPos and SetTarget functions, which are self-explanatory.

So lets put all this junk together! Open up CApp.h and modify it to include the new header files:
#include "Define.h"
#include "CArea.h"
#include "CCamera.h"
Also, add a new function prototype:
void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);
We are going to checking that event for move our map around with the keyboard. So, open up CApp_OnEvent.cpp and add the function:
void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_UP:      CCamera::CameraControl.OnMove( 0,  5); break;
        case SDLK_DOWN:  CCamera::CameraControl.OnMove( 0, -5); break;
        case SDLK_LEFT:  CCamera::CameraControl.OnMove( 5,  0); break;
        case SDLK_RIGHT: CCamera::CameraControl.OnMove(-5,  0); break;

        default: {
        }
    }
}
We check the SDL keys, and according to the key move the camera around. Next, open up CApp_OnCleanup.cpp and add the cleanup function for the Area:
CArea::AreaControl.OnCleanup();
Open up CApp_OnRender.cpp and add the Render call too:
CArea::AreaControl.OnRender(Surf_Display, CCamera::CameraControl.GetX(), CCamera::CameraControl.GetY());
Notice that we are passing the Camera coordinates to the OnRender. And lastly, open up CApp_OnInit.cpp:
if(CArea::AreaControl.OnLoad("./maps/1.area") == false) {
    return false;
}

SDL_EnableKeyRepeat(1, SDL_DEFAULT_REPEAT_INTERVAL / 3);
SDL_EnableKeyRepeat call sets the keyboard repeat count. So if we hold down a key it will keep calling the event above. And we are done! Compile the code and try it out. I hope it all works as expected, this was a very lengthy tutorial. Please check out the tutorial files below if you are having a bit of trouble. And please let me know if for some reason I left some important code out.


SDL Collision
If you are still trying to catch your breath from the last tutorial, I recommend you sit down, because this next tutorial won't be any nicer. In this tutorial we will be extending our entity class a bit further with collisions, and movement. So hold on to your pants, and get ready.

The first order of business we'll tackle is movement. A seemingly simple idea that can be a bit tricky. Now, I am going to introduce a method that has worked for me for quiet some time, though I am not sure if it can be readily optimized, changed, or whatever. So save the criticism. The reason I like it so much is that it's pixel perfect now matter what system you are on, and it's already fast for its purpose.

But before we get into that, we're going to need a standard for movement. You see, when we play our games on old Pentium 2 as opposed to a quad core, there are apparent differences in speed. So, if we did a simple X++; on the older machine, the entity would move slower than the newer machine. So the idea is simple, figure out the frame rate of the machine, and base our movements off of the frame rate. I call that the speed factor. Now, in order for this to work, we have to have a base frame rate. I usually pick 32, as that is a good number. So if our machine had a frame rate of 32, our speed factor would be 1. If our frame rate was 64, our speed factor would be 0.5. We move twice as slower on the faster machine, that way speeds are consistent across different machines.

So, lets create this class. Create two new files called CFPS.h and CFPS.cpp. Open up the header file first:
#ifndef _CFPS_H_
    #define _CFPS_H_

#include <SDL.h>

class CFPS {
    public:
        static CFPS FPSControl;

    private:
        int     OldTime;
        int     LastTime;

        float     SpeedFactor;

        int    NumFrames;
        int     Frames;

    public:
        CFPS();

        void    OnLoop();

    public:
        int     GetFPS();

        float   GetSpeedFactor();
};

#endif
First, we have a single control for accessing our speed facor, and frame rate. Again, a singleton object that we should be used to using by now. Next, we have OldTime, which is used to calculate the Frames per second. Then we have LastTime, which is used to calculate the speed factor. SpeedFactor is the current speed factor of the game (of course), NumFrames is the games current FPS, and Frames is the frame count when calculating the frames per second (explanation for all these below). Notice that all of them are private members, so to access our FPS and SpeedFactor we have two get functions. We don't need anyone messing with those variables directly. Then, we have a simple constructor, and an OnLoop function to calculate everything.

Now, open up CFPS.cpp to add the actual code:
#include "CFPS.h"

CFPS CFPS::FPSControl;

CFPS::CFPS() {
    OldTime     = 0;
    LastTime    = 0;

    SpeedFactor    = 0;

    Frames      = 0;
    NumFrames   = 0;
}

void CFPS::OnLoop() {
    if(OldTime + 1000 < SDL_GetTicks()) {
        OldTime = SDL_GetTicks();

        NumFrames = Frames;

        Frames = 0;
    }

    SpeedFactor = ((SDL_GetTicks() - LastTime) / 1000.0f) * 32.0f;

    LastTime = SDL_GetTicks();

    Frames++;
}

int CFPS::GetFPS() {
    return NumFrames;
}

float CFPS::GetSpeedFactor() {
    return SpeedFactor;
}
I thought I'd drop a note in here, since I have had some people miss this; whenever you make a variable of a class static, you also need to declare it elsewhere. Notice I have CFPS::FPSControl at the top. If you fail to do this, you'll get an undeclared error.

Okay, we have a basic constructor, setting everything to 0. Next, we have our OnLoop function where all the magic happens. The first calculation we do, is finding the Frames per Second of the game. This is where SDL_GetTicks comes in. SDL_GetTicks returns the number of milliseconds that have passed since we have called SDL_Init. So, effectively, 1000 milliseconds equals 1 second. Now, notice the condition I am checking. I am checking if the OldTime (which is currently set to 0) + 1000, is less than the current tick count. Think about it, if OldTime is 0 + 1000, and 1001 milliseconds have passed, that means at least 1 second has passed. Why is that important? That tells use to restart the FPS counter.

Notice inside the if statement we then set OldTime to SDL_GetTicks, set NumFrames to the Frames count, and reset our Frames counter. So, going with the example above, OldTime now becomes 1001. Now, 1001 + 1000, is the next calculation, so another second must pass. Concurrent with this, is the Frames++ at the bottom of that function. This continuelly counts up, until the OldTime resets it. I hope that is easy to understand.

Next, we have the actual calculation for the SpeedFactor. Like I mentioned earlier, the SpeedFactor determines how fast to move objects. Now, for a better explanation. Say we are on a really slow computer that gets 1 FPS per second. That's a really bad computer by the way. We have the base movement rate set at 32 (our made up number). So, ideally, any computer will move objects 32 pixels per second. So this 1 FPS computer will move the object 32 pixels every second. Now, jump to a 10 FPS computer. We still want to move 32 pixels per second. So, that's 1/10 of 32 right? So, for each calculation we move 3.2 pixels, and after 1 second, we'll have moved 32 pixels.

To calculate this, we base it on how fast the computer is currently going. This is what LastTime is for. LastTime holds the time it took for the last loop in the game. So, in the constructor it's currently 0. Now, say we have an FPS of 10. That means we have 10 loops in the game every second. How many milliseconds is that? That's 100 milliseconds. So, 100 / 1000 (for 1 second), is 1/10. The same number we got above. Now multiply this by our desired movement rate, and we have our Speed Factor! I'll be showing you how to use this later on, just try to understand how we calculated it.

And lastly, we have our two get functions that return NumFrames (FPS), and the SpeedFactor.

Okay, so much already and we still have a long way to go!

From the last tutorial we loaded an area, 1.area, we'll be leaving this, as well as everything else from that code. But, we're going to take one thing out right now. Open up CApp_OnEvent.cpp and remove the following:
case SDLK_UP:      CCamera::CameraControl.OnMove( 0,  5); break;
case SDLK_DOWN:  CCamera::CameraControl.OnMove( 0, -5); break;
case SDLK_LEFT:  CCamera::CameraControl.OnMove( 5,  0); break;
case SDLK_RIGHT: CCamera::CameraControl.OnMove(-5,  0); break;
We aren't going to be controlling the camera anymore, but we'll be controlling our main Player who will dictate where the camera goes. Note: In the last tutorial I had some values backwards for the camera! Please read the notes in the last tutorial for the fix. This new tutorial will have the fixes already in place, but without any explanation.

Now, lets go ahead and add this new Player to the game. But first, some explanation. Remember, anything that moves in the game or that can be interacted with (besides the Map), is an Entity. So, our main Player will be an entity. But, Player is distinct, he likes certain things other Entities do not. So, instead of simply doing CEntity Player; we need to create a class just for Player. But! Before we can even to get to making the Player class, we're going to need to extend the Entity class. Things like this include the ability to Move an Entity, Collision (what we've all been waiting for), Animation states, and Entity Flags. Jumping and a few other things will have to wait until the next lesson (sorry).

So open up CEntity.h and change it to the following:
#ifndef _CENTITY_H_
#define _CENTITY_H_
#include <vector>
#include "CArea.h"
#include "CAnimation.h"
#include "CCamera.h"
#include "CFPS.h"
#include "CSurface.h"

enum {
    ENTITY_TYPE_GENERIC = 0,

    ENTITY_TYPE_PLAYER
};

enum {
    ENTITY_FLAG_NONE     = 0,

    ENTITY_FLAG_GRAVITY    = 0x00000001,
    ENTITY_FLAG_GHOST    = 0x00000002,
    ENTITY_FLAG_MAPONLY    = 0x00000004
};

class CEntity {
    public:
        static std::vector<CEntity*>    EntityList;

    protected:
        CAnimation      Anim_Control;

        SDL_Surface*    Surf_Entity;

    public:
        float           X;
        float           Y;

        int             Width;
        int             Height;

        bool        MoveLeft;
        bool        MoveRight;

    public:
        int        Type;

        bool        Dead;
        int        Flags;

    protected:
        float        SpeedX;
        float        SpeedY;

        float        AccelX;
        float        AccelY;

    public:
        float        MaxSpeedX;
        float        MaxSpeedY;

    protected:
        int             CurrentFrameCol;
        int             CurrentFrameRow;

    protected:
        int        Col_X;
        int        Col_Y;
        int        Col_Width;
        int        Col_Height;

    public:
        CEntity();

        virtual ~CEntity();

    public:
        virtual bool OnLoad(char* File, int Width, int Height, int MaxFrames);

        virtual void OnLoop();

        virtual void OnRender(SDL_Surface* Surf_Display);

        virtual void OnCleanup();

        virtual void OnAnimate();

        virtual void OnCollision(CEntity* Entity);

    public:
        void    OnMove(float MoveX, float MoveY);

        void     StopMove();

    public:
        bool    Collides(int oX, int oY, int oW, int oH);

    private:
        bool     PosValid(int NewX, int NewY);

        bool     PosValidTile(CTile* Tile);

        bool     PosValidEntity(CEntity* Entity, int NewX, int NewY);
};

class CEntityCol {
    public:
        static std::vector<CEntityCol>    EntityColList;

    public:
        CEntity* EntityA;
        CEntity* EntityB;

    public:
        CEntityCol();
};

#endif
Oh geez, what happened, and where did all this code come from?! Sorry, for all of you who just fainted, maybe you'll understand a little bit more now why this tutorial is so long and took so long. It's a lot to handle, let alone trying to teach to everyone. One step at a time.

First, we have the basic things still there, OnLoad, OnLoop, OnRender, and OnCleanup. The two new virtual functions are OnAnimate and OnCollision. Now, OnAnimate is used to define Animation States, like I mentioned earlier. This is useful for selecting the right animation when the Player moves left or right. The second, OnCollision, is called when an Entity collides with another Entity.

So, for example, if EntityA and EntityB collide, what happens? Well, first, neither can pass through each other; that's the easy part. The other part, is that EntityA needs to know EntityB collided with him. Such as, Mario falling on a spike. The spike notifies Mario, "Hey, I killed you! Change your animation and die." Well, the thing is, we cannot notify the other entity when the collision happens. One important reason is that, if the Spike kills Mario, Mario may not have been able to notify the Goomba that he killed him. So, what we do instead is store everything in a queue. This is the new vector called CEntityColList. This stores any collision that have happened when all the entities have moved. And once all movement has taken place, that is when we'll notify all the Entities of collisions, and call OnCollision.

Whew. Also, in correlation with this, is Collides function, which is used to determine the actual collision (more on this later). Also with this, is four Col_ variables. These variables are used to offset a collision for an entity. Back in previous tutorials we had a Yoshi image we were using. If you notice, it would make little sense to use the width of the image for collision purposes. Yoshi doesn't actually come to the edges of the images; so, we have to offset it a little, so when Yoshi runs into a wall it actually looks like his nose is hitting the wall, and not being a few pixels away. And since these variables are used to collision purposes only, I have prefixed them with Col_.

Now, for some movement explanation. Whenever an Entity wants to move, we do not move it to where it wants to go immediately. What we do instead, is move it in the direction we want it to go at a certain speed. Remember the whole CFPS class? That's why time based movement is so important. So, in the new OnMove function, we pass it how fast we want our Entity to go. If we want the Entity to move 1 pixel a second to the right, we would do OnMove(1, 0). Now, I wish it was this simple, but it's not. In real life we don't move in steps. We move according to speed and acceleration. Meaning, it takes us a little time to start moving at first (inertia). It looks unnatural to have entities jumping around.

So, to get past this, we have six variables SpeedX, SpeedY, AccelX, AccelY, MaxSpeedX, and MaxSpeedY. Using these is simple. First, the MaxSpeed variables cap how fast the entity can go; if we didn't cap it, the Entities would continue to go faster and faster into infinity. The SpeedX and SpeedY are the current speed of the entity. If these were 1, again, that would be 1 pixel per second. Now, the AccelX and AccelY, determine how fast we increase or decrease our speed. If AccelX and AccelY are 0, then we aren't moving at all. If AccelX is 1, then our SpeedX is increasing over time. Now, this is where it gets a little confusing with the Speed factor.

I want my player to increase in speed 1 pixel per second. This would be:
AccelX = 1;

SpeedX += AccelX * CFPS::FPSControl.GetSpeedFactor();
Notice we multiply the AccelX by the Speed Factor. That's because we want to increase the speed 1 pixel every second. If I wanted to increase the speed 1 pixel every loop, I would leave the speed factor out of it. Now, when OnMove receives these Speed variables, it also has to figure in the speed factor:
MoveX *= CFPS::FPSControl.GetSpeedFactor();
MoveY *= CFPS::FPSControl.GetSpeedFactor();
(MoveX and MoveY are the arguments passed to OnMove). Now, the SpeedX and SpeedY only determine how fast we want to move per second. So we have to multiply it by the speed factor. A little hint for you people out there, the entire speed of the game is determined by the Speed Factor! If we changed the Speed Factor of the game, it would determine how fast anything in the games moves. Slow motion deaths anyone?

Now, whenever an Entity moves, there are some things to consider. Say in one loop we are supposed to move the Entity 4 pixels (slow computer perhaps, or really fast entity). Now, lets say 2 pixels away there is a wall. Now, if we checked 4 pixels in front of use, we wouldn't let our guy move. But, we still want him to move as close as he can. So, to move this 2 pixel distance, we have to check every pixel along the way. Now, you might be thinking this will be slow. Well, not really. Someone with a 100 FPS who wants to move 5 pixels a second (a reasonable speed), will move less than one pixel every loop (0.05 to be exact). So, every loop, this entity moves 0.05 pixels over. That means we have 1 or 2 checks when checking for collisions for that entity. More explanation on this later when we actually implement moving.

Before an entity is allowed to move, we have to check a few things. The first is if the entity collides with the map, and the second is if the entity collides with another entity. This is where the three PosValid functions come in. The first, PosValid(), is the main function that handles the other two. It checks to see if the tile the entity is trying to move to is valid, and also checks the entities. The other two PosValid functions determine what a valid tile is, and what a valid entity is. Again, more explanation later.

Now, last for movement, we have MoveLeft, MoveRight, Flags, and StopMove. Now, MoveLeft and MoveRight are simply flags, true or false, that determine if the entity is moving left or right. If true, the AccelX will be set a value, causing the entity to move. Now, StopMove simply sets the AccelX in the opposite direction until we reach 0. Again, we don't stop suddenly in real life, we gradually stop. The last item, Flags, is used to determine a few things. In the second enum, you'll notice several flags.
ENTITY_FLAG_NONE
ENTITY_FLAG_GRAVITY
ENTITY_FLAG_GHOST
ENTITY_FLAG_MAPONLY
These are used to determine certain properties about an entity. For example, an entity that is affected by gravity will have the ENTITY_FLAG_GRAVITY turned on. An Entity that can go through walls, will have ENTITY_FLAG_GHOST turned on. An entity that only collides with the map, and not other entities, will have ENTITY_FLAG_MAPONLY turned on. Now, notice how we have them numbered in hex. This is because we're treating the Flags in binary. This way, we can activate multiple flags at once:
Flags = ENTITY_FLAG_GRAVITY | ENTITY_FLAG_GHOST;

To check for a single flag is simple:

if(Flags & ENTITY_FLAG_MAPONLY) {
    //Map only
}
Wow, what a load, but still more in the class to explain. Next, we have some Animation variables. There are only two of them: CurrentFrameCol, and CurrentFrameRow. These two determine where on our image we start to find individual frames of an animation. A CurrentFrameCol and CurrentFrameRow would be the top left of the image. Moving over one column would change things. Think of it like tilesets. Look at the example image below:

(Save this Image for this Tutorial)

Last, but not least, we have variables to indicate if a variable is dead, and the type of entity. You'll notice we have another enum, like the Flags, to specify the type of entity. This is important when dealing with collision events. So, back to the Mario and Spike example; lets say a Goomba happens to step in a spike. Now, in Mario world, the Goomba would not die. So, the Spike needs to know that entities type. Simple enough I think.

Okay, lets get into the core of the class now. Open up CEntity.cpp and lets have some more fun. Since we have had so many drastic changes, I am going to go through this line by line with the basics, and then move on to specific areas of the class. So, starting at the top we have our basic constructor, and statics:
std::vector<CEntity*>     CEntity::EntityList;

std::vector<CEntityCol> CEntityCol::EntityColList;

CEntityCol::CEntityCol() {
    EntityA = NULL;
    EntityB = NULL;
}

CEntity::CEntity() {
    Surf_Entity = NULL;

    X = 0;
    Y = 0;

    Width     = 0;
    Height     = 0;

    MoveLeft  = false;
    MoveRight = false;

    Type =     ENTITY_TYPE_GENERIC;

    Dead = false;
    Flags = ENTITY_FLAG_GRAVITY;

    SpeedX = 0;
    SpeedY = 0;

    AccelX = 0;
    AccelY = 0;

    MaxSpeedX = 5;
    MaxSpeedY = 5;

    CurrentFrameCol = 0;
    CurrentFrameRow = 0;

    Col_X = 0;
    Col_Y = 0;

    Col_Width  = 0;
    Col_Height = 0;
}
Everything is simply set to 0, NULL, or some respective value. One thing to notice is the MaxSpeed variables. Set these to whatever you like. Usually this will depend on the particular entity that is in the game. Such as Mario will be able to move faster than a Goomba. Notice that I have grouped two classes into one file; that is okay to do sometimes! OnLoad is the exact same, so skip over that, and then move on to OnLoop:
void CEntity::OnLoop() {
    //We're not Moving
    if(MoveLeft == false && MoveRight == false) {
        StopMove();
    }

    if(MoveLeft) {
        AccelX = -0.5;
    }else

    if(MoveRight) {
        AccelX = 0.5;
    }

    if(Flags & ENTITY_FLAG_GRAVITY) {
        AccelY = 0.75f;
    }

    SpeedX += AccelX * CFPS::FPSControl.GetSpeedFactor();
    SpeedY += AccelY * CFPS::FPSControl.GetSpeedFactor();

    if(SpeedX > MaxSpeedX)  SpeedX =  MaxSpeedX;
    if(SpeedX < -MaxSpeedX) SpeedX = -MaxSpeedX;
    if(SpeedY > MaxSpeedY)  SpeedY =  MaxSpeedY;
    if(SpeedY < -MaxSpeedY) SpeedY = -MaxSpeedY;

    OnAnimate();
    OnMove(SpeedX, SpeedY);
}
Lots of new things here. First, remember MoveLeft and MoveRight are used to indicate if the entity is moving left or right (duh). If both are these are false, we call StopMove, which will handle settings Acceleration in the reverse direction so we come to a gradual stop. Below that, if MoveLeft or MoveRight are true, we give AccelX a value. 0.5 is an arbitrary number, you probably want to mess with this until you find a value that you like. Just below that, we have a condition checking if gravity is turned on for that particular entity. If it is, we set AccelY. This will cause our entity to fall down. Again, this is whatever value you want. The larger the number, the faster you will fall. Next, below that we have our SpeedX and SpeedY. Again, we increase the Speed of the entity over time in correlation to our Speed Factor. Then, below that, we check against our MaxSpeed variables to make sure we only go a certain speed. Finally, we call OnAnimate for our entity to animate, and then OnMove, which will actually move our entity.

Moving on, we have a few changes to OnRender:
void CEntity::OnRender(SDL_Surface* Surf_Display) {
    if(Surf_Entity == NULL || Surf_Display == NULL) return;

    CSurface::OnDraw(Surf_Display, Surf_Entity, X - CCamera::CameraControl.GetX(), Y - CCamera::CameraControl.GetY(), CurrentFrameCol * Width, (CurrentFrameRow + Anim_Control.GetCurrentFrame()) * Height, Width, Height);
}
First, we have added the Camera X and Y to the rendering coordinates for the entity. This makes the entities render in connection to where the camera is. This way, if the camera moves, all the entities will in respect to the camera position. Next, we have added CurrentFrameCol and CurrentFrameRow to the X2 and Y2 of OnDraw. This makes it so we can set different animations.

The first column has Yoshi facing the left, while the second column has Yoshi facing to the right. When we move to the left, we will want to set CurrentFrameCol = 0, so that Yoshi appears to be walking left. And when we move to the right, we will set CurrentFrameCol = 1, so that Yoshi appears to be walking to the right.

Skip over OnCleanup, nothing new there. We then have OnAnimate:
void CEntity::OnAnimate() {
    if(MoveLeft) {
        CurrentFrameCol = 0;
    }else

    if(MoveRight) {
        CurrentFrameCol = 1;
    }

    Anim_Control.OnAnimate();
}
This is fairly straight forward. Like I said above, depending on which direction we are moving, we set the animation. Next, we have OnCollision:
void CEntity::OnCollision(CEntity* Entity) {
}
Yay, an empty function. This function will be called whenever two entities hit each other. This entire time we've been creating an class for basic entities. In order to take advantage of this function, you will need to create our own class (like CPlayer, which we'll do later on in this tutorial) and override this function. This is because the action taken by an entity depends upon it's type. Like my earlier examples, a Goomba will not react the same way to a collision with a spike as Mario would.

Next, we our OnMove function.
void CEntity::OnMove(float MoveX, float MoveY) {
    if(MoveX == 0 && MoveY == 0) return;

    double NewX = 0;
    double NewY = 0;

    MoveX *= CFPS::FPSControl.GetSpeedFactor();
    MoveY *= CFPS::FPSControl.GetSpeedFactor();

    if(MoveX != 0) {
        if(MoveX >= 0)     NewX =  CFPS::FPSControl.GetSpeedFactor();
        else             NewX = -CFPS::FPSControl.GetSpeedFactor();
    }

    if(MoveY != 0) {
        if(MoveY >= 0)     NewY =  CFPS::FPSControl.GetSpeedFactor();
        else             NewY = -CFPS::FPSControl.GetSpeedFactor();
    }

    while(true) {
        if(Flags & ENTITY_FLAG_GHOST) {
            PosValid((int)(X + NewX), (int)(Y + NewY)); //We don't care about collisions, but we need to send events to other entities

            X += NewX;
            Y += NewY;
        }else{
            if(PosValid((int)(X + NewX), (int)(Y))) {
                X += NewX;
            }else{
                SpeedX = 0;
            }

            if(PosValid((int)(X), (int)(Y + NewY))) {
                Y += NewY;
            }else{
                SpeedY = 0;
            }
        }

        MoveX += -NewX;
        MoveY += -NewY;

        if(NewX > 0 && MoveX <= 0) NewX = 0;
        if(NewX < 0 && MoveX >= 0) NewX = 0;

        if(NewY > 0 && MoveY <= 0) NewY = 0;
        if(NewY < 0 && MoveY >= 0) NewY = 0;

        if(MoveX == 0) NewX = 0;
        if(MoveY == 0) NewY = 0;

        if(MoveX == 0 && MoveY     == 0)     break;
        if(NewX  == 0 && NewY     == 0)     break;
    }
}
This function is quite a bit to take in. Lets see if we can straighten it all out. First, MoveX and MoveY are the where we want to move our entity. Remember up above, we call this passing our SpeedX and SpeedY. So, calling OnMove(1, 0) we would want to move 1 pixel to the right every second. At the top, we check our Speed, and if 0, we don't do anything (duh). Below that, we do something I explained earlier. We multiply MoveX and MoveY by the speed factor. This gives us the correct movement per second. Just below that, we figure out the values for two new variables, NewX and NewY. These will be our X and Y increments. Remember, I mentioned above that this method is pixel perfect, so we need to inch closer and closer to our desired position. Now, we set our NewX and NewY to our speed factor because this is how much we should use to inch closer and closer to our desired position.

Below that, we have a seemingly infinite loop. This is where the movement magic happens and collision. First, we have a condition to check if the entity is a ghost, if it is, we increase the X and Y regardless if a collision has happened. We still call PosValid though, because this will notify entities of any collision that have taken place. Notice in PosValid we pass the current X and Y, along with adding NewX and NewY. This is where we want to go, so we check if that spot is empty.

If the entity is not a ghost, we do normal collision. We check X and Y separatly because we may be able to move up but not forward (think jumping into a wall). If the position is valid (empty), we increase the X or Y, if not, we set SpeedX or SpeedY to 0 which causes you to immediately stop (this is as close to reality as we need, if you do run into a wall you usually stop immediately). Now, below that, we change MoveX and MoveY depending upon our NewX. Say our MoveX was 2, and our NewX was 0.5. We would slowly decrease MoveX until it reaches 0. Once it does, we have come to our desired position and we are done. The 8 if statements below check if MoveX and MoveY have reached 0, and break out of the loop.

Moving on, we have StopMove:
void CEntity::StopMove() {
    if(SpeedX > 0) {
        AccelX = -1;
    }

    if(SpeedX < 0) {
        AccelX =  1;
    }

    if(SpeedX < 2.0f && SpeedX > -2.0f) {
        AccelX = 0;
        SpeedX = 0;
    }
}
This one is fairly straight forward. Depending on our Speed, we set the Acceleration in the reverse direction. So, if we are moving right, we set the acceleration to the left. The last if statement is simply a boundary to how slow we need to be going until we stop. If we didn't do that, it's possible that we could be stuck in a loop of moving left to right.

Next, we have a function that determines collision of two boxes. I will say right now that this function was borrowed from Cone3D (), and then modified. If it works, why fix it?

Anyway, lets take a look at it to see if I can help explain. At the top we define 8 variables, to define 8 sides. 4 sides are for the first object, and 4 sides are for the second object. Basically, we are doing bounding box collision:

So, in a very basic sense, we want to check if two boxes have overlapped. The easiest method, is to determine if we have not collided on all 4 sides. So first, we determine the locations of all the sides:
int left1, left2;
int right1, right2;
int top1, top2;
int bottom1, bottom2;

int tX = (int)X + Col_X;
int tY = (int)Y + Col_Y;

left1 = tX;
left2 = oX;

right1 = left1 + Width - 1 - Col_Width;
right2 = oX + oW - 1;

top1 = tY;
top2 = oY;

bottom1 = top1 + Height - 1 - Col_Height;
bottom2 = oY + oH - 1;
Look at the figure below to get an idea:

The top and left, are the X,Y of the object, and the right and bottom, are the X+Width-1, Y+Height-1. We subtract one in order to get the true coordinate of the side.

Next, we finally do the checking. What we do here is to check if one of the 4 sides of the first object are out of range of the second object. If one is, then we cannot possibly be colliding. But, if all 4 checks fail, we are colliding. So, if bottom1 is less than top2, that means the Bottom side of the first object is higher up on the screen then the Top of the second object:
if (bottom1 < top2) return false;
if (top1 > bottom2) return false;

if (right1 < left2) return false;
if (left1 > right2) return false;
Finally, if all checks pass, then we return true for a collision. One note, remember that the Col_ variables are used to simply offset the size of the entity. If the entity is 32 width, but we offset by 2, that means when we check collision we use the Width of 30. Here's how our final Collides method looks:
bool CEntity::Collides(int oX, int oY, int oW, int oH) {
    int left1, left2;
    int right1, right2;
    int top1, top2;
    int bottom1, bottom2;

    int tX = (int)X + Col_X;
    int tY = (int)Y + Col_Y;

    left1 = tX;
    left2 = oX;

    right1 = left1 + Width - 1 - Col_Width;
    right2 = oX + oW - 1;

    top1 = tY;
    top2 = oY;

    bottom1 = top1 + Height - 1 - Col_Height;
    bottom2 = oY + oH - 1;

    if (bottom1 < top2) return false;
    if (top1 > bottom2) return false;

    if (right1 < left2) return false;
    if (left1 > right2) return false;

    return true;
}
Moving on we find some more fun. We have our 3 last functions, the 3 PosValid functions. Overall, this functions basically determine if a collision has taken place. Remember, earlier OnMove will call PosValid to determine if a requested position is open to move to. So, keep in mind that NewX, and NewY in these functions is where the Entity wants to move to, not where it currently is.

Lets first look at PosValid. This function will determine any type of collision that has taken place, and return true or false. Now, this function basically has only two type of collision, Entity to Entity, and Map to Entity. That's what PosValidTile and PosValidEntity are used for.
bool CEntity::PosValid(int NewX, int NewY) {
    bool Return = true;

    int StartX     = (NewX + Col_X) / TILE_SIZE;
    int StartY     = (NewY + Col_Y) / TILE_SIZE;

    int EndX    = ((NewX + Col_X) + Width - Col_Width - 1)         / TILE_SIZE;
    int EndY    = ((NewY + Col_Y) + Height - Col_Height - 1)    / TILE_SIZE;

    for(int iY = StartY;iY <= EndY;iY++) {
        for(int iX = StartX;iX <= EndX;iX++) {
            CTile* Tile = CArea::AreaControl.GetTile(iX * TILE_SIZE, iY * TILE_SIZE);

            if(PosValidTile(Tile) == false) {
                Return = false;
            }
        }
    }

    if(Flags & ENTITY_FLAG_MAPONLY) {
    }else{
        for(int i = 0;i < EntityList.size();i++) {
            if(PosValidEntity(EntityList[i], NewX, NewY) == false) {
                Return = false;
            }
        }
    }

    return Return;
}
Now, the first collision we check for is a Map Collision. This is done by first grabbing 4 variables about our Entity. Basically, we want to know all the TileIDs that this entity is over. This is done by grabbing the X (and the Col_ offset) and dividing by the TILE_SIZE. This gives us our TileID for our X. The same goes for the Y variable. These two calculations determine our StartX and StartY. Now, EndX and EndY are determined in a similiar fashion, but we want to also add the Width and Height (just like we determined sides in the Collision function above). This will give us an area of tiles that this entity is over:
int StartX     = (NewX + Col_X) / TILE_SIZE;
int StartY     = (NewY + Col_Y) / TILE_SIZE;

int EndX    = ((NewX + Col_X) + Width - Col_Width - 1)     / TILE_SIZE;
int EndY    = ((NewY + Col_Y) + Height - Col_Height - 1)    / TILE_SIZE;
Now that we have the Start and End of tiles, we simply loop through them:
for(int iY = StartY;iY <= EndY;iY++) {
    for(int iX = StartX;iX <= EndX;iX++) {
    }
}
Inside of this loop, this is where we check each tile to see if it's a valid tile to move into. It the tile happens to be a wall, we would set the Return variable to false, to indicate that this requested position (hence the name PosValid) is not good:
CTile* Tile = CArea::AreaControl.GetTile(iX * TILE_SIZE, iY * TILE_SIZE);

if(PosValidTile(Tile) == false) {
    Return = false;
}
Notice the new function GetTile, we'll define this later on in this tutorial. But to explain it here, GetTile will return the CTile of any X and Y coordinate. After we get the tile, we pass it on to PosValidTile for it to figure out if the tile can be moved over or not. So, for a moment, jump on over to PosValidTile:
bool CEntity::PosValidTile(CTile* Tile) {
    if(Tile == NULL) {
        return true;
    }

    if(Tile->TypeID == TILE_TYPE_BLOCK) {
        return false;
    }

    return true;
}
Remember the enum in CTile.h at the top? Those enums were a list of types that you could give a tile. They are used here now, to determine if a tile can be moved on or not. So, that's basically what we do here. First, if the Tile is NULL (which you should always check your pointers), we return true (meaning, yes, they can walk on this tile). Next, we check if the tile is a TILE_TYPE_BLOCK, if so, then no, the entity cannot walk on this tile. And finally, for all other types, yes, they can walk on this tile. Simple enough.

Now, jump back to the PosValid function. You will then find another loop:
if(Flags & ENTITY_FLAG_MAPONLY) {
}else{
    for(int i = 0;i < EntityList.size();i++) {
        if(PosValidEntity(EntityList[i], NewX, NewY) == false) {
            Return = false;
        }
    }
}
First, we do a simple check with our Flags again, this will check if we have the ENTITY_FLAG_MAPONLY flag turned on. If so, this means we're only checking collisions against the map, so we don't worry about Entity collisions. If we are checking collisions, then we jump to the loop. The loop is fairly simply, we go through the EntityList and pass each entity to the PosValidEntity, along with NewX and NewY. So, Jump on over to PosValidEntity:
bool CEntity::PosValidEntity(CEntity* Entity, int NewX, int NewY) {
    if(this != Entity && Entity != NULL && Entity->Dead == false &&
        Entity->Flags ^ ENTITY_FLAG_MAPONLY &&
        Entity->Collides(NewX + Col_X, NewY + Col_Y, Width - Col_Width - 1, Height - Col_Height - 1) == true) {

        CEntityCol EntityCol;

        EntityCol.EntityA = this;
        EntityCol.EntityB = Entity;

        CEntityCol::EntityColList.push_back(EntityCol);

        return false;
    }

    return true;
}
Okay, this function is actually quite simple. We do some checks first on the Entity (the Entity we are wanting to collide with) to determine that this Entity is not the same Entity we're moving (Mario cannot collide with Mario). That's the "this != Entity." It simply checks to make sure the Entities are not the same. We then check to make sure the Entity is not NULL, not Dead, and doesn't have the ENTITY_FLAG_MAPONLY flag turned on. So far, so good. If all these checks pass, then we finally pass it to the Collides function of that Entity, passing along our Entities coordinates and Width/Height (with Col_ offsets). Remember, if any part of an if statement fails, like this != Entity, none of the other code in the if statement will be executed.

So, if the Entities do collide, finally, we use our new CEntityCol class. This will be a queue of collisions that have taken place. So, we push our Entity into EntityA, and the other Entity into EntityB, and then push it into the list. Finally, we return false, to indicate that this position is not valid. Wow, we are almost done!

Don't rush out to compile, it won't work. Why? Because, remember that GetTile function? :)

So, let's add this into our code. Open CArea.h and add two functions at the bottom of the class:
public:
    CMap*    GetMap(int X, int Y);

    CTile*    GetTile(int X, int Y);
Then, open CArea.cpp and add the functions:
CMap* CArea::GetMap(int X, int Y) {
    int MapWidth  = MAP_WIDTH * TILE_SIZE;
    int MapHeight = MAP_HEIGHT * TILE_SIZE;

    int ID = X / MapWidth;
        ID = ID + ((Y / MapHeight) * AreaSize);

    if(ID < 0 || ID >= MapList.size()) {
        return NULL;
    }

    return &MapList[ID];
}

CTile* CArea::GetTile(int X, int Y) {
    int MapWidth  = MAP_WIDTH * TILE_SIZE;
    int MapHeight = MAP_HEIGHT * TILE_SIZE;

    CMap* Map = GetMap(X, Y);

    if(Map == NULL) return NULL;

    X = X % MapWidth;
    Y = Y % MapHeight;

    return Map->GetTile(X, Y);
}
I know, I know... they are more complicated than they look. First, look at GetMap. GetMap simply returns a CMap pointer at the given coordinates. It's just like GetTile, but for Maps. Now, how it works is first by determining the size of our maps. We then translate our X,Y into an ID, and then return the CMap pointer. I won't go into how translating X,Y into an ID works, because I believe this is explained in the last tutorial.

Now, go to GetTile. We determine the MapWidth and MapHeight, again (and I suppose this can be optimized), and then grab the Map with GetMap. We do a simple check for a NULL pointer (hint, hint), and then turn our X and Y into Tile coordinates. So, if our TILE_SIZE was 16, and our X was at 128, our Tile coordinate for X would be 8. Meaning, we are 8 tiles over. We finally then send this over to the Map GetTile. (Oh yes, one other function). This is why we have a GetMap function, if you are wondering. Rememember, CArea is a bunch of maps making one big area. So, we will have to determine which map is needed when grabbing tiles.

So, jump over to CMap.h now, and add this at the bottom:
public:
    CTile*    GetTile(int X, int Y);
Then, open CMap.cpp and add this at the bottom:
CTile* CMap::GetTile(int X, int Y) {
    int ID = 0;

    ID = X / TILE_SIZE;
    ID = ID + (MAP_WIDTH * (Y / TILE_SIZE));

    if(ID < 0 || ID >= TileList.size()) {
        return NULL;
    }

    return &TileList[ID];
}
This is basically just like GetMap, but uses the TileList instead.

We're almost done!

Just a few more changes to go. Create two new files called CPlayer.h and CPlayer.cpp. This class will be our main player and will extend our new CEntity class. So, open up CPlayer.h:
#ifndef _CPLAYER_H_
#define _CPLAYER_H_
#include "CEntity.h"

class CPlayer : public CEntity {
    public:
        CPlayer();

        bool OnLoad(char* File, int Width, int Height, int MaxFrames);

        void OnLoop();

        void OnRender(SDL_Surface* Surf_Display);

        void OnCleanup();

        void OnAnimate();

        void OnCollision(CEntity* Entity);
};

#endif
I won't explain any of this other than it's overriding all the virtual functions from CEntity.

Now, open up CPlayer.cpp:
#include "CPlayer.h"

CPlayer::CPlayer() {
}

bool CPlayer::OnLoad(char* File, int Width, int Height, int MaxFrames) {
    if(CEntity::OnLoad(File, Width, Height, MaxFrames) == false) {
        return false;
    }

    return true;
}

void CPlayer::OnLoop() {
    CEntity::OnLoop();
}

void CPlayer::OnRender(SDL_Surface* Surf_Display) {
    CEntity::OnRender(Surf_Display);
}

void CPlayer::OnCleanup() {
    CEntity::OnCleanup();
}

void CPlayer::OnAnimate() {
    if(SpeedX != 0) {
        Anim_Control.MaxFrames = 8;
    }else{
        Anim_Control.MaxFrames = 0;
    }

    CEntity::OnAnimate();
}

void CPlayer::OnCollision(CEntity* Entity) {
}
Again, just like CEntity, but simply overriding the functions. One minor note, look at OnAnimate. In there we check our SpeedX to make a modification to our MaxFrames. Basically, if SpeedX != 0, (which means we are moving) we set our MaxFrames to 8. If we aren't moving we set it to 0. Now, remember our Yoshi animation above, it has 8 frames for walking. So, if our Speed is not 0, we make Yoshi walk, otherwise we make it look like he's standing still. This is the area where you would define other animations, like Idle, Walking, Running, Jumping, and so on.

Now, open up CApp.h and add the header file at the top:
#include "CPlayer.h"
Then, add two Players in your class:
CPlayer    Player;
CPlayer Player2;
Then, open up CApp_OnInit.cpp and initialize them:
if(Player.OnLoad("yoshi.png", 64, 64, 8) == false) {
    return false;
}

if(Player2.OnLoad("yoshi.png", 64, 64, 8) == false) {
    return false;
}

Player2.X = 100;

CEntity::EntityList.push_back(&Player);
CEntity::EntityList.push_back(&Player2);
Simple enough. We set PlayerX, the other Yoshi, over, so that we're not on top of each other initially. Now, just below this add this extra code:
CCamera::CameraControl.TargetMode = TARGET_MODE_CENTER;
CCamera::CameraControl.SetTarget(&Player.X, &Player.Y);
Remember I said earlier, we're going to make the camera follow our Yoshi. Now, open up CApp_OnRender and change this:
CArea::AreaControl.OnRender(Surf_Display, CCamera::CameraControl.GetX(), CCamera::CameraControl.GetY());
To this:
CArea::AreaControl.OnRender(Surf_Display, -CCamera::CameraControl.GetX(), -CCamera::CameraControl.GetY());
This change is also related to the Camera bug I mentioned above earlier. Open up CCamera.h and change the TargetX and TargetY from ints to floats. Also change the SetTarget ints to floats.

One last change now! Open up CApp.h and add this to your code (you should already have OnKeyDown):
void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode);
Then, open up CApp_OnEvent.cpp:
void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_LEFT: {
            Player.MoveLeft = true;
            break;
        }

        case SDLK_RIGHT: {
            Player.MoveRight = true;
            break;
        }

        default: {
        }
    }
}

void CApp::OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_LEFT: {
            Player.MoveLeft = false;
            break;
        }

        case SDLK_RIGHT: {
            Player.MoveRight = false;
            break;
        }

        default: {
        }
    }
}
To go over this quickly, we basically check if Left or Right is pressed, and set MoveLeft or MoveRight on our Entity to true. If the key is depressed, we set it to false.

Now, we're done! Remember to grab the Yoshi picture above to make sure he shows up right. Also, I changed the 1.map to have TILE_TYPE_BLOCKs in it. You can grab an updated one here: Example Map. Since this tutorial is so lengthy, and took me a long time (sorry), please be sure to let me know of any bugs or something I forgot to address. Thanks.

P.S.
In the next tutorial, we'll see how to react to collisions that happen. In this tutorial, though we pushed collisions into a queue, we didn't do anything with the queue yet.


SDL Collision Events
Finally! The next tutorial. This is the 2nd part to the SDL Collision tutorial. We'll be looking at collision events, the part we left unfinished in the last tutorial. To refresh your memory, a collision event is an event that is stored in queue after a collision has taken place. Take our recent shooter contest for an example. When the player fires a bullet, the bullet flies through the air, and then hits the enemy. The moment the bullet and the enemy collide, an event is triggered and stored in the queue. After all movement has taken place, the queue is iterated through, and events can appropriately respond.

So lets get started. Open up your project from the last tutorial. We're first going to make a few prelimary changes. Make a new file called CEntityCol.cpp. Inside that file, put the following:
#include "CEntity.h"

std::vector<CEntityCol> CEntityCol::EntityColList;

CEntityCol::CEntityCol() {
    this->EntityA = NULL;
    this->EntityB = NULL;
}
Now, get rid of that code from CEntity.cpp. I am basically moving the code to its own file so that everything is much neater. The second thing. Open up CEntity.h and change:
virtual void OnCollision(CEntity* Entity);
To this:
virtual bool OnCollision(CEntity* Entity);
Also, change the void to bool in CEntity.cpp, CPlayer.h, and CPlayer.cpp. Why the change? Well, you'll see in a minute. ;)

We're already storing the collision events in a queue, now we need to do something with them. Open up CApp_OnLoop.cpp. You'll notice we already have a loop going for entities and we're calling OnLoop. We're basically going to do the same thing again, but after our first loop:
for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnLoop();
}

//Collision Events
for(int i = 0;i < CEntityCol::EntityColList.size();i++) {
    CEntity* EntityA = CEntityCol::EntityColList[i].EntityA;
    CEntity* EntityB = CEntityCol::EntityColList[i].EntityB;

    if(EntityA == NULL || EntityB == NULL) continue;

    if(EntityA->OnCollision(EntityB)) {
        EntityB->OnCollision(EntityA);
    }
}

CEntityCol::EntityColList.clear();
Not much going on here. We're first iterating through every event in the queue. We then grab the two entities involved (EntityA, and EntityB), and we make sure they are valid pointers. If so, we first tell EntityA that it has collided with EntityB by calling OnCollision. Now, this is where that void to bool change comes in. EntityA can choose to prevent EntityB from knowing it has collided with him. If you remember, only Entities that are moving check for collision. This would mean EntityA is definitly a moving entity, and EntityB may or may not be moving.

Here's an example to explain why you wouldn't want to waste time calling EntityB's OnCollision. Say you fire a bullet, and it hits a rock entity. Now, lets say the rock has a flag stating it's indestructable. Our rock doesn't care its been hit. So, when the OnCollision is called for the bullet, the bullet can see the entity it has hit is indestructable. And because of that, it can create a little "bullet being destroyed" animation, and then we can ignore telling the rock about it.

This is about the extent of entity collisions. So, now that we have this base, lets do something semi-useful with it.

Open up CEntity.h and add this new function, and variable:
protected:
    bool    CanJump;

public:
    bool     Jump();
Then, open up CEntity.cpp, and add the Jump function. Also make CanJump = false in the constructor:
CEntity::CEntity() {
    CanJump = false;

    // ... Other code ...
}

bool CEntity::Jump() {
    if(CanJump == false) return false;

    SpeedY = -MaxSpeedY;

    return true;
}
Finally, go to the OnMove function. We're going to figure out when our entity is allowed to jump. An entity is allowed to jump whenever it is touching the ground - or, in other words, whenever the entity is no longer moving on the positive Y-axis. So, at the top, we are first always going to reset CanJump to false:
CanJump = false;
Now, go down a bit, and modify the PosValid for the Y-axis to the following:
if(PosValid((int)(X), (int)(Y + NewY))) {
    Y += NewY;
}else{
    if(MoveY > 0) {
        CanJump = true;
    }

    SpeedY = 0;
}
Now, to run through everything we did. First, we have a flag that states if an entity can jump. When set to true, we are allowed to call the Jump function. This CanJump variable is always false, unless our feet are on the ground. Whenever we actually do want to jump, we simply set our SpeedY in the negative Y-axis direction (up). Remember, gravity is always pushing us down, but at a slow speed. So, if we have a SpeedY of -5, and a AccelY of 0.75, we'll move up for a time, gravity will kick in, and then we will eventually be pushed back down. Thus, giving the effect of a real jump. The trick here is to get the Speed and Acceleration variables just right.

Also, as a side note, the reason we made Jump return true/false is simply for informational purposes. Meaning, when a key is pressed by the player to jump, they won't always be allowed to jump. And, if you wanted, you could do something based upon what Jump() returns.

So a few more tweaks before we try this out. In the constructor of CEntity, change the MaxSpeedX and MaxSpeedY to 10. Then, open up CApp_OnEvent.cpp and add the following to OnKeyDown:
case SDLK_SPACE: {
    Player.Jump();
    break;
}
And one last thing. Open up CPlayer.cpp, and change the OnCollision function to this:
bool CPlayer::OnCollision(CEntity* Entity) {
    Jump();

    return true;
}
That's it! Compile, run, and have fun with Yoshi jumping all over the place. You'll notice the simple collision event we added is that when Yoshi 1 collides with Yoshi 2, it causes Yoshi 1 to jump. Kind of lame, but you should get the idea.


SDL Tutorial - Tic Tac Toe
Up to this point we have been laying the foundation for developing a game. So far we've setup a basic structure to handle common routines, we've setup a special class to handle events, and we've also setup a class to handle a few surface functions. In this tutorial we'll take all those things, combine them, and create a tic-tac-toe game. Don't worry, things should be pretty simple. Use the last tutorial to build off of.

The first thing we are going to need to do is plan our game. From experience, we know that tic-tac-toe has a 3x3 grid, where you place X's and O's. So, we know that we will need 3 graphics, one for the grid, one for the X, and one for the O. We don't need multiples of the X or O, because we can draw them in the program as many times as we like. Lets eliminate this first step. Our grid is going to be 600x600, and our X's and O's will be 200x200 (1/3 of the area).

Grid
O X

Now that we have our images, we are going to need a way to load them into our program. Open up CApp.h and make some modifications. Remove the Test Surface, and add three new surfaces.
#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

#include "CEvent.h"
#include "CSurface.h"

class CApp : public CEvent {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

    private:
        SDL_Surface*    Surf_Grid;

        SDL_Surface*    Surf_X;
        SDL_Surface*    Surf_O;

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

                void OnExit();

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
Also, open up CApp.cpp and make some modifications. Remove the Test Surface again, and add the three new ones again.
#include "CApp.h"

CApp::CApp() {
    Surf_Grid = NULL;
    Surf_X = NULL;
    Surf_O = NULL;

    Surf_Display = NULL;

    Running = true;
}

int CApp::OnExecute() {
    if(OnInit() == false) {
        return -1;
    }

    SDL_Event Event;

    while(Running) {
        while(SDL_PollEvent(&Event)) {
            OnEvent(&Event);
        }

        OnLoop();
        OnRender();
    }

    OnCleanup();

    return 0;
}

int main(int argc, char* argv[]) {
    CApp theApp;

    return theApp.OnExecute();
}
And, you guessed it, open up CApp_OnCleanup.cpp to make some modifications there too. Just like before, get rid of the Test Surface and add the three new ones.
#include "CApp.h"

void CApp::OnCleanup() {
    SDL_FreeSurface(Surf_Grid);
    SDL_FreeSurface(Surf_X);
    SDL_FreeSurface(Surf_O);
    SDL_FreeSurface(Surf_Display);
    SDL_Quit();
}
Now that we have the surfaces setup, lets load them into memory. Open up CApp_OnInit.cpp, and make some changes. Get rid of the test surface (again), and load the three new ones. Be sure to put the correct filenames. Also, change the dimensions of the window to 600x600, the size of the grid. This make sure we don't have any blank space around the window we aren't using.
#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(600, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    if((Surf_Grid = CSurface::OnLoad("./gfx/grid.bmp")) == NULL) {
    return false;
    }

    if((Surf_X = CSurface::OnLoad("./gfx/x.bmp")) == NULL) {
    return false;
    }

    if((Surf_O = CSurface::OnLoad("./gfx/o.bmp")) == NULL) {
    return false;
    }

    return true;
}
You may have noticed a change I made in the filenames. I added ./gfx/ before the filename to specify which folder the graphics are in. As games begin to grow it's very practical to have all the files in one folder. Because of such, from hereon, all images will be put in the gfx folder. Now, let's get the grid showing up on the screen. Open up CApp_OnRender.cpp and change the test surface rendering to make the grid render.
#include "CApp.h"

void CApp::OnRender() {
    CSurface::OnDraw(Surf_Display, Surf_Grid, 0, 0);

    SDL_Flip(Surf_Display);
}
Try compiling your program, and if successful, you should see the grid show up. Remember, there are basically 5 steps to using a surface: declare it, set it to NULL, load it, draw it, and then free it. It's good practice to learn all these 5 steps now, because later on if you neglect one of these steps if can cause problems. For example, neglecting to set a surface to NULL can cause undefined behavior, or neglecting to free a surface can cause a memory leak.

You may have noticed something odd about the graphics we are using, the X and O contain a pink background. There is a reason for it, we are going to implement transparency onto these surfaces. Basically, wherever there is pink, it will show through; we will make the pink color transparent. SDL offers a simple function to do this, SDL_SetColorKey. To implement this, open up CSurface.h so we can add a new function.
#ifndef _CSURFACE_H_
    #define _CSURFACE_H_

#include <SDL.h>

class CSurface {
    public:
        CSurface();

    public:
        static SDL_Surface* OnLoad(char* File);

        static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);

        static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H);

        static bool Transparent(SDL_Surface* Surf_Dest, int R, int G, int B);
};

#endif
Now, to implement this function, open up CSurface.cpp and add the function:
bool CSurface::Transparent(SDL_Surface* Surf_Dest, int R, int G, int B) {
    if(Surf_Dest == NULL) {
        return false;
    }

    SDL_SetColorKey(Surf_Dest, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(Surf_Dest->format, R, G, B));

    return true;
}
Notice 3 extra arguments being passed besides the surface. These are the 3 color values that we want to make transparent, it doesn't have to be just pink. For instance, it we wanted red to be transparent it would be 255, 0, 0.

This function first checks to see if we have a valid surface. If so, we set a color key (transparency) for a color. The first argument is the surface to apply the color key to, the second is some flags telling SDL how to perform the operation, and the third is the color to make transparent. The flags being applied are basic, the first tells SDL to apply the color key to the source (the surface being passed) and the second tells SDL to try to use RLE acceleration (basically, try to make drawing later on faster). The third argument is a little bit more complex; we are using SDL_MapRGB in order to create a color. SDL_MapRGB takes a surface, and your requested color (R, G, B), and tries to match it as close as it can to that surface. You might be thinking why this is useful. Not all surfaces have the same color palette. Remember the old NES days where there was only a few colors that could be used? Same idea here, SDL_MapRGB takes a color and matches it with the closest color on that surface palette.

Lets apply this new function to our surfaces now, open up CApp_OnInit.cpp and make the following changes:
#include "CApp.h"

bool CApp::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }

    if((Surf_Display = SDL_SetVideoMode(600, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
    }

    if((Surf_Grid = CSurface::OnLoad("./gfx/grid.bmp")) == NULL) {
        return false;
    }

    if((Surf_X = CSurface::OnLoad("./gfx/x.bmp")) == NULL) {
        return false;
    }

    if((Surf_O = CSurface::OnLoad("./gfx/o.bmp")) == NULL) {
        return false;
    }

    CSurface::Transparent(Surf_X, 255, 0, 255);
    CSurface::Transparent(Surf_O, 255, 0, 255);

    return true;
}
Everything for the surfaces should be set. The next thing we have to do is figure out a way to draw these X's and O's. We can't just draw them everywhere on the grid because they won't always be in the same spots. What we are going to have to do is make an array of 9 containers, the values in this array will tell us the values for each cell on the grid. So, spot 0 would be the top left, 1 would be the top middle, 2 the top right, 3 the middle left, and so on. Create this array by adding it to CApp.h:
#ifndef _CAPP_H_
    #define _CAPP_H_

#include <SDL.h>

#include "CEvent.h"
#include "CSurface.h"

class CApp : public CEvent {
    private:
        bool            Running;

        SDL_Surface*    Surf_Display;

    private:
        SDL_Surface*    Surf_Grid;

        SDL_Surface*    Surf_X;
        SDL_Surface*    Surf_O;

    private:
        int        Grid[9];

    public:
        CApp();

        int OnExecute();

    public:
        bool OnInit();

        void OnEvent(SDL_Event* Event);

            void OnExit();

        void OnLoop();

        void OnRender();

        void OnCleanup();
};

#endif
We know that each cell can have three possible values: Empty, X, and O. These tell us what is currently in that cell. To make things a little neater than having 0, 1, 2 as values, we'll use an enum instead. If you are umfamiliar with how an enum works, try finding a quick tutorial on them. Just know that GRID_TYPE_NONE = 0, GRID_TYPE_X = 1, and GRID_TYPE_O = 2. Go back to CApp.h and add the following underneath the Grid array:
enum {
    GRID_TYPE_NONE = 0,
    GRID_TYPE_X,
    GRID_TYPE_O
};
Note, up to this point I have been displaying practically all the code when I am referring to different files. From hereon, I expect you to know where code goes. Most of the time I will tell you where to place it by, and sometimes I might display all the code. Now that we have a way to know what a cell is, we're going to need a way to reset the board. Let's create a new function at the bottom inside of CApp.h called Reset.
public:
    void Reset();
Open up CApp.cpp and add the following function just before int main:
void CApp::Reset() {
    for(int i = 0;i < 9;i++) {
        Grid[i] = GRID_TYPE_NONE;
    }
}
This loop will set every cell in the grid to GRID_TYPE_NONE, meaning that all the cells are empty. We're going to have to do this at the very beginning when our program is loaded, so make a call to this function within the CApp_OnInit.cpp file:
//...

CSurface::Transparent(Surf_X, 255, 0, 255);
CSurface::Transparent(Surf_O, 255, 0, 255);

Reset();
So far so good. The next thing we are going to have to do is make the ability to place X's and O's on the screen. Lets create a new function that will handle this. Open up CApp.h again and add the function just below Reset.
void SetCell(int ID, int Type);
Now, open back up CApp.cpp and add the function:
void CApp::SetCell(int ID, int Type) {
    if(ID < 0 || ID >= 9) return;
    if(Type < 0 || Type > GRID_TYPE_O) return;

    Grid[ID] = Type;
}
This function takes two arguments, the first is the cell ID to change, and the second is the type to change it to. We have to conditions here, the first is to make sure we don't go outside the bounds of the array (if we did it would likely crash our program), and the second is to make sure we passed an appropriate type. Simple enough. Now, let's implement a way to draw the X's and O'. Open up CApp_OnRender and add the following code just after the grid:
for(int i = 0;i < 9;i++) {
    int X = (i % 3) * 200;
    int Y = (i / 3) * 200;

    if(Grid[i] == GRID_TYPE_X) {
        CSurface::OnDraw(Surf_Display, Surf_X, X, Y);
    }else
    if(Grid[i] == GRID_TYPE_O) {
        CSurface::OnDraw(Surf_Display, Surf_O, X, Y);
    }
}
This is a little bit more complex than we have been used to so far. Firstly, we are looping through each cell in the grid. Next, we are translating that grid ID over to X and Y coordinates. We do this in two different ways. To find X, we take the remainder of i to 3. This will give us 0 when i is 0, 1 when i is 1, 2 when i is 2, 0 when i is 3, and so on. We multiply it by 200 because each cell is 200x200 pixels. To find Y, we divide by 3, this causes Y to be 0 when i is 0, 1, 2, Y to be 1 when i is 3, 4, 5, and so on. We then multiply that by 200 as well. I encourage you to really try to understand what is going on here because this sort of method is used for tile based games.

Next, all we have to do is check the cell type, and then draw the correct surface at that cell.

Now that we have our surfaces drawing, we'll need a way to communicate from the user to the computer. We'll use mouse events for this. When the users clicks a cell it will set the cell appropriately. We are going to need to overload one of the CEvent functions for this. Open up CApp.h and add the following function just below OnEvent and next to OnExit.
void OnLButtonDown(int mX, int mY);
Now, open up CApp_OnEvent.cpp and add the function:
void CApp::OnLButtonDown(int mX, int mY) {
    int ID    = mX / 200;
    ID = ID + ((mY / 200) * 3);

    if(Grid[ID] != GRID_TYPE_NONE) {
        return;
    }

    if(CurrentPlayer == 0) {
        SetCell(ID, GRID_TYPE_X);
        CurrentPlayer = 1;
    }else{
        SetCell(ID, GRID_TYPE_O);
        CurrentPlayer = 0;
    }
}
First, we are doing the reverse of what we did with translating to X and Y from an ID, this time we are translating to an ID. We then make sure that that cell hasn't already been taken, if it has, we return out of the function. Next, we are checking which players turn it is, set the cell appropriately, and then switch turns. CurrentPlayer is a new variable to specify whose turn it is, so we'll need to add this. Open up CApp.h and add the variable below the grid array:
int CurrentPlayer;
Also, set the default value for this variable in CApp.cpp:
CApp::CApp() {
    CurrentPlayer = 0;

    Surf_Grid = NULL;
    Surf_X = NULL;
    Surf_O = NULL;

    Surf_Display = NULL;

    Running = true;
}
Try compiling the program and you should have a mostly working version of tic-tac-toe. Congratulations!

This is where you take the rest of it yourself. We have a solid foundation for our game, and most the work done. I encourage you to take this a step further. Try adding a "X Won", "O Won", and "Draw" at the end of each game (extra images here). Think, how are you going to check who won (a function for this purpose would fit well)? Try adding a way to reset the game after it's done. If you are brave, try to add some generic AI that will play against the user. And if you are even braver, try adding the ability to play player vs. player, or player vs. computer.